F - Substring 2 Editorial by kyopro_friends


indexは \(0\) から始まるとし、文字列 \(S,T\)\(01\) 数列と同一視する。 また、文字列 \(S\) の長さを \(|S|\)\(i\) 文字目を \(S[i]\) とそれぞれ表す。文字列 \(S\)\(i\) 文字目からの連続する \(|T|\) 文字からなる部分文字列を \(S_i\) とおく。

求めるべきは \(\min_i \{ S_i \text{ と } T \text{ で異なっている文字数} \}\) だが、これはかわりに \(\max_i\{ S_i \text{ と } T \text{ で一致している文字数}\}\) を求め、\(|T|\) から引けば求まる。後者で考える。

\(i\) について \(S_i\)\(T\) で一致している文字数が求まれば良い。文字の種類ごとに分けて考えることで、これは 「\(T[k]=0\) かつ \(S[i+k]=0\) である \(k\) の個数」+「\(T[k]=1\) かつ \(S[i+k]=1\) である \(k\) の個数」に等しいことがわかる。こうして分解された2つの問題は、\(0,1\) が入れ替わっただけの同じ問題であるため、一方が解ければ他方も同様に解ける。後者について考える。

\(S,T\)\(01\) 数列であるため、「\(T[k]=1\) かつ \(S[i+k]=1\) である \(k\) の個数」は \(\sum_k T[k]*S[i+k]\) に等しい。したがって、これを全ての \(i\) について高速に求めれば良い。

\(T\) を reverse するとこの式は\(\sum_k T[|T|-1-k]*S[i+k] = \sum_{p+q=|T|-1+i} T[p]*S[q]\) となるため FFT を用いることで全ての \(i\) についてを \(O(|S| \log |S|)\) で求めることができる。以上により元の問題が解けた。

実装例(C++)

#include <bits/stdc++.h>
#include <atcoder/all>
using namespace std;
using namespace atcoder;

using Modint=modint998244353;
void chmax(int& a, int b){if(a<b)a=b;}

int main(){
    string S, T;
    cin >> S >> T;
    reverse(T.begin(), T.end());
    const int slen=S.size(), tlen=T.size();
    vector<Modint> s0(slen), s1(slen), t0(tlen), t1(tlen);
    for(int i=0;i<slen;i++)(S[i]=='0'?s0:s1)[i]=1;
    for(int i=0;i<tlen;i++)(T[i]=='0'?t0:t1)[i]=1;
    vector<Modint> st0=convolution(s0, t0);
    vector<Modint> st1=convolution(s1, t1);
    int ans=0;
    for(int i=tlen-1;i<slen;i++) chmax(ans, st0[i].val() + st1[i].val());
    cout << tlen-ans << endl;
}

posted:
last update: