2020年12月26日土曜日

CodeChef - Counting Strings

問題: 与えられた文字列ssについて、左から比較しても右から比較しても辞書順で大きい文字列ttを数える。

ttの構成を考えると、2つの位置i,j(ij)i, j (i \le j)を境界にして、以下のように左から順に5つの部分に分解できる。(2番目と4番目は1文字)

  • ssと完全一致
  • s[i]<t[i]s[i] < t[i]
  • 文字自由
  • s[j]<t[j]s[j] < t[j]
  • ssと完全一致

N:=sN := |s|とし、0-basedで考える。f(i):=f(i) := 文字s[i]s[i]より大きい文字の数(s[i]=as[i] = \operatorname{a}ならf(i)=25f(i)=25)とすると、i,ji, jが異なる場合の数え上げはi<jf(i)f(j)26ji1\sum_{i < j} f(i)f(j)26^{j-i-1}である。あとはi,ji, jが一致する場合、つまりssttがちょうど1字異なる場合を別に数えてf(0)+...+f(N1)f(0)+...+f(N-1)を足す。

上の式を見ると、畳み込める形になっている。g(x)=f(Nx1)g(x) = f(N-x-1)として一方の添え字を逆順にすると、26N2i+jN2f(i)g(j)(1/26)i+j26^{N-2}\sum_{i+j \le N-2} f(i)g(j) (1/26)^{i+j}と変形できて、ffggをFFTで畳み込んだ後で1/261/26を掛けながら加算すればよい。mod\bmod109+710^9+7だが、登場する数が十分に小さい(高々25210525^2 \cdot 10^5)ので998244353998244353でやっても問題ない。


よく見ると、変形して累積和を取るだけだった。

i<jf(i)f(j)26ji1=126i=0N1(f(i)26ij=i+1N1f(j)26j) \sum_{i < j} f(i)f(j)26^{j-i-1} = \frac{1}{26}\sum_{i=0}^{N-1} \biggl( \frac {f(i)}{26^i} \sum_{j = i+1}^{N-1}{f(j)}26^j \biggr )

形を見て畳み込みできそうと思えるのは悪くないとしても、変数を分離できないかまず考えるほうがさすがによい気がする。