Official

A - Similarity Editorial by sounansya


「どの文字列 \(S_i\) も、\(T\) と少なくとも \(1\) 箇所で文字が一致する」という条件を噛み砕いてみます。

条件の逆を考えます。「少なくとも \(1\) 箇所で一致する」の逆は「どの箇所でも一致しない」なので、条件の逆は「ある文字列 \(S_i\) が存在し、\(T\) とどの箇所の文字も一致しない」となります。

\(S_i\)\(T\) がどの箇所の文字も一致しない」という条件は、\(S_i'\)\(S_i\)01 を全て反転させた文字列に対して「\(S_i'\)\(T\) が一致する」となります。つまり、問題は以下のように言い換えることができます。

\(S_1',S_2',\ldots,S_N'\) どれとも一致しないような長さ \(M\)01 文字列 \(T\) が存在するか判定し、存在する場合は一つ求めよ。

長さ \(M\)01 文字列は \(2^M\) 通り存在するので、\(N < 2^M\) であれば条件を満たす文字列は必ず存在し、\(N=2^M\) であれば条件を満たす文字列は存在しません(つまり、No を出力してプログラムを終了すれば良いです)。以降は \(N < 2^M\) の場合を考えます。

条件を満たす文字列を一つ求める方法はありますが、ここでは乱択による解法を紹介します。具体的には、以下のようなアルゴリズムで答えを探すことができます。

  • 入力で与えられる文字列の列 \(S_1,S_2,\ldots,S_N\) から各桁の 01 を反転させた文字列 \(S_1',S_2',\ldots,S_N'\) を作成する。
  • \(S_1',S_2',\ldots,S_N'\) を持つ set を用意する。
  • 以下を繰り返す:
    • 長さ \(M\)01 文字列 \(T\) をランダムに作成する。つまり、各桁に対して 01 かをそれぞれ独立に \(\displaystyle \frac12\) の確率で選び決定する。
    • \(T\) が上で作成した set の中に含まれる場合は繰り返しの最初に戻る。
    • \(T\) が上で作成した set の中に含まれない場合は \(T\) を答えとして出力し、プログラムを終了する。

答えが出力される場合その答えが正しいことは明らかなので、上のアルゴリズムが高確率で十分高速に動くことを示せば良いです。

上のアルゴリズムが遅くなる場合は長さ \(M\)01 文字列の中で条件を満たす文字列の個数が小さく条件を満たさない文字列が多い場合です。条件を満たさない文字列の個数は \(N\) 個です。条件を満たす文字列の個数の最小値は \(1\) 個であるため、条件を満たす文字列の個数が \(1\) 個である場合に十分高速に動作することを示せば良いです。

\(T\) の生成は毎回独立にランダムなので、条件を満たさない文字列を生成してしまう確率は \(\displaystyle \frac{N}{N+1}\) です。\(N\) は最大で \(N_{\text{max}}=2\times 10^4\) なので、この確率は \(\displaystyle \frac{N_{\text{max}}}{N_{\text{max}}+1}\) 以下であると見積もることができます。

\(2\times 10^5\) 回生成してまだ条件を満たす文字列が見つからない確率を考えると、上の見積もりにより \(\displaystyle \left(\frac{N_{\text{max}}}{N_{\text{max}}+1}\right)^{2\times 10^5}\) 以下であることが分かります。この値は実際に計算すると \(0.0000453886\) 程度であり、実際はこの値よりも十分小さいことも加味すると上の乱択でほぼ確実に十分高速に動作することが保証できます。

乱択ではなく、01 文字列を一つずつ昇順に確かめることで確実に正答することもできます(が、実装量が少し増えると思います。多倍長整数や 128bit 整数などを使い整数として持つとかなり扱いが楽になります)。

実装例(Python3)

from random import randint

n, m = map(int, input().split())
s_inv = set()
for i in range(n):
    s = input()
    ss = ""
    for c in s:
        ss += "01"[c == "0"]
    s_inv.add(ss)
if n == 2**m:
    print("No")
    exit()
while True:
    t = ""
    for i in range(m):
        t += "01"[randint(0, 1)]
    if t in s_inv:
        continue
    print("Yes")
    print(t)
    exit()

posted:
last update: