F - Preimage Resistance

2 secs 1024 MB
uni_kakurenbo's icon uni_kakurenbo

概要

少し実装量が多めですが,一つ一つ課題を解決していくことで解くことができます.
また,少し深く考察をして実装量を減らすことも可能です.

問題原案:uni_kakurenbo

略解

先頭から求める

Pi:=(P_i := (先頭 ii 項を決定したとき,残りの MiM - i 項は何通りの選び方ができるか)) とします.

KK をデクリメントして KK が 0-based の前提で考えると,求めるべき列の 11 項目は,先頭が H1H_1 であるような文字列のうち辞書順で KP1\lfloor \frac{K}{P_1} \rfloor 番目に小さいものであり,残りの M1M - 1 項は『P1P_1 通りの選び方の中から辞書順で KmodP1K \bmod P_1 番目に小さいもの』を求めればよいです.
結局,再帰的に同じ処理を繰り返して答えを求めることができます.(『カッコ内』がちょうど,大きさの小さい同様の問題になっています.)

PiP_i は簡単な DP によって求めることができますが,その際は乗算のオーバーフローに注意してください.

末尾から求める

1010 進法からの基数変換の要領で,末尾から順に求めていくこともできます.
先頭から求めていった場合に比べて,実装量は低減されるはずです.

解説

d[h]:=(S(i)1=hd[h] := ({S^{(i)}}_1 = h であるような ii の個数) とします.

.Ⅰ. 先頭から順に決定する

Pi:=(P_i := (先頭 ii 項を決定したとき,残りの MiM - i 項は何通りの選び方ができるか)) とします.
Pi=d[HM]×d[HM1]××d[Hi+1]P_i = d[H_M] \times d[H_{M-1}] \times \cdots \times d[H_{i + 1}] ですから,これは次に示すような DP によって ii の降順に O(M)O(M) 時間で計算できます.

  • PM=1P_M = 1
  • For  keach[M1,M2,,0]:\text{For}\;k \xleftarrow{\text{each}} [M-1,M-2,\ldots,0]:
    • Pi=Pi+1×d[Hi+1]P_i = P_{i+1} \times d[H_{i+1}]

以降 KK をデクリメントして KK が 0-based の前提で考えます.

求めるべき列の 11 項目は,先頭が H1H_1 であるような文字列のうち辞書順で KP1\lfloor \frac{K}{P_1} \rfloor 番目に小さいものであるといえます.
残りの M1M - 1 項は『P1P_1 通りの選び方の中から辞書順で KmodP1K \bmod P_1 番目に小さいもの』を求めればよいです.

この残った M1M - 1 項の求め方が,元の問題と全く同じということに気づいたでしょうか.

これを利用することで,次のように先頭から順々に求めていくことができます.

  • 11 項目(MM 項のうちの先頭)を求める.
    • これは KP1\lfloor \frac{K}{P_1} \rfloor 番目に小さいもの.
    • 残りの M1M - 1 項目は,P1P_1 通りの中で KmodP1K \bmod P_1 番目に小さいもの.
    • KKmodP1K \gets K \bmod P_1
  • 22 項目(末尾 M1M-1 項のうちの先頭)を求める.
    • これは KP2\lfloor \frac{K}{P_2} \rfloor 番目に小さいもの.
    • 残りの M2M - 2 項目は,P2P_2 通りの中で KmodP2K \bmod P_2 番目に小さいもの.
    • KKmodP2K \gets K \bmod P_2
  • 33 項目(末尾 M2M-2 項のうちの先頭)を求める.
  • \vdots
  • MM 項目(末尾 11 項のうちの先頭)を求める.
    • これは KPM\lfloor \frac{K}{P_M} \rfloor 番目に小さいもの.
    • 残りは 00 項.

注記: PiP_i のオーバーフロー対策

N,MN, M の制約を考えると,PiP_i の値を求める段階でオーバーフローが生じてしまう可能性が非常に高いです.
これは,K<1018K < 10^{18} という制約を利用して,積が 101810^{18} を超える場合は強制的に 101810^{18} とするようにするとよいです.
(KK はデクリメントして 0-based で考えていることに注意してください.)

ABC169 - B でこれと同等の処理をする問題が出題されています.

.Ⅱ. 末尾から順に決定する

より実装量の少ない解法を紹介します.

答えとなる列の ii 項目としてあり得る文字列が d[Hi]d[H_i] 通りあることは明らかです.

答えの ii 項目となる文字列を直接求めるのではなく,「答えの ii 項目は,それとしてあり得る d[Hi]d[H_i] 通りのもののうちで何番目に小さいものか」を求めるとします.

たとえば,入出力例 11 について考えてみます.

KK の値は簡単のために 0-based で考えたいので,デクリメントして K=5K=5 とします.

  • 11 項目としてあり得るものは,辞書順で昇順に the, topcoder22 通り
  • 22 項目としてあり得るものは,辞書順で昇順に grand, gray22 通り
  • 33 項目としてあり得るものは,辞書順で昇順に competition, contest22 通り

です.
したがって,答えである,辞書順で 55 番目に小さい列 ((topcoder, grand, contest)) は,

  • 11 項目は,22 通りのうち 11 番目に小さい topcoder
  • 22 項目は,22 通りのうち 00 番目に小さい grand
  • 33 項目は,22 通りのうち 11 番目に小さい contest

ですから,(1,0,1)(1, 0, 1) と表せることになります.(便宜上これらも 0-based で考えます.)
以降このような表記方法を「(文字列の列の)数列表現」と呼ぶこととします.

この数列を求めます.

この数列の各項について,ii 項目の数値は明らかに 00 以上 d[Hi]d[H_i] 未満です.

もう一度入力例 11 に戻って考えます.
答えとしてあり得る列の数列表現を辞書順に列挙してみましょう.
これは,辞書順の定義より明らかに (0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0),(1,1,1)(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1) となります.

いかがですか?
00 以上 88 未満の数値を 22 進数で表記したものと全く同じであることがポイントです.

1010 進数で与えられた K=5K = 522 進数に変換すると,101101 ですから,先ほど考えた答えの列の数列表現とも一致します.

今回の入出力例 11 が,ちょうど 22 進数を用いて考えることができたのは,d[H1]=d[H2]=d[H3]=2d[H_1] = d[H_2] = d[H_3] = 2 であったからです.

この考え方はより一般化することができ,あらゆる入力に対する答えの数列表現は「ii 桁目に,00 以上 d[Hi]d[H_i] 未満の d[Hi]d[H_i] 種類の数字を用いることができるような数値表現で,KK の値を表したときの各桁の値」に対応するといえます.

これは,1010 進表現から他の基数による表現へ変換する際と同じ要領で簡単に実装することが可能です.

.Ⅲ. 時間計算量

いずれの解法でも,各 1iM1 \leq i \leq M について先頭が HiH_i であるような SS の(昇順に並び替えられた)一覧が必要です.

これは事前に一度 SS をソートすればよいので,全体を通しての時間計算量は O(NlogN+M)O(N \log N + M) となります.

.Ⅲ. 発展

1)1) 類題

実装例

C++
C++
Python
Python

\\

余談

実装問題でごめんなさい...