F - Avoid K Palindrome 2 解説 by magurofly

埋め込みによる解法

この問題は、入力の種類数を減らす工夫をすることで、解を埋め込むことが可能です。

この問題の解は文字の順番を変えたり、ある文字を別の文字と入れ替えたりしても変化しません。例えば、サンプル2では \(S\)zzyyx ですが、 \(S\)xyzzyaabbclevel に置き換えたとしても答えは変わりません。

そのため、各文字ごとに \(S\) 中の出現回数を数え、文字を無視して出現回数の分布だけ見ることで、複数の \(S\) をひとつに纏めることができます。例えば zzyyx であれば、 x\(1\) 個、 y\(2\) 個、 z\(2\) 個であるため、出現回数を降順に並べ \((2, 2, 1)\) と表すことができます。

ある \(N\) に対して考える必要がある \(S\) の種類数は \(N\) の分割数 \(p(N)\) と等しくなります。 例えば、 \(N=5\) の場合は \(p(5) = 7\)\(N=10\) の場合は \(p(10) = 42\) です。 なお、分割数の説明はWikipediaに譲ることとします。

このように考えると、入力の種類数は各 \(N = 1, 2, \ldots, 10\) ごとに \(K\)\(N\) 種類で \(S\)\(p(N)\) 種類、あわせると \(\sum_{N = 1}^{10} N p(N) = 1106\) 個となり、これは解を埋め込んでもソースコード長制限に引っかからない程度の大きさです。

整数の分割の列挙

解の全列挙には整数の分割を列挙する必要があるため、以下ではその方法を紹介します。

非負整数 \(n\)\(1\) 以上 \(m\) 以下の整数に分割する方法は、以下のとおり再帰的に実装することができます。

  • \(n = 0\) のとき: 空列の \(1\) 通りです。
  • \(n > 0\) のとき: 各 \(i = 1, 2, \ldots, \min\{n, m\}\) について、分割の先頭は \(i\) となり、のこりは \(n - i\)\(1\) 以上 \(i\) 以下の整数に分割する方法と同じになります。

これをジェネレータを使って実装すると、以下のようになります。

def partition(n, m):
    if n == 0:
        yield ()
        return
    for i in reversed(range(1, min(n, m) + 1)):
        for rest in partition(n - i, i):
            yield (i, ) + rest

コードの生成

dict に入力と解の組み合わせを入れていくことで、非常に簡単に埋め込むことができます。

以下はコードを生成するコードの一例です。

from more_itertools import distinct_permutations

def partition(n, m):
    if n == 0:
        yield ()
        return
    for i in reversed(range(1, min(n, m) + 1)):
        for rest in partition(n - i, i):
            yield (i, ) + rest

def solve(K, S):
    N = len(S)
    ans = 0
    for p in distinct_permutations(S):
        if not any(p[i:i + K] == tuple(reversed(p[i:i + K])) for i in range(N - K + 1)):
            ans += 1
    return ans

# dict に入力と解のペアを入れる
embedded = {}
for N in range(1, 10 + 1):
    for K in range(1, N + 1):
        for S_key in partition(N, N):
            # 入力を生成
            S = []
            for i in range(len(S_key)):
                S += [i] * S_key[i]
            embedded[(K, S_key)] = solve(K, S)

# コードを生成
print(f"""
from collections import defaultdict

N, K = map(int, input().split())
S = input()

count = defaultdict(int)
for c in S:
    count[c] += 1

S_key = tuple(reversed(sorted(count.values())))

embedded = {embedded}

print(embedded[(K, S_key)])
""")

なお、コードの生成には数分程度を要するため実行の際はご注意ください。

このコードで生成された提出: Python (27 ms)

投稿日時:
最終更新: