Official

F - SSttrriinngg in StringString Editorial by yuto1115

解説

まずは \(k\) に関して二分探索をします。以降、ある \(k\) に対して、\(g(T,k)\)\(f(S,N)\) の部分文字列であるかどうか判定する方法について考えます。

以下、\(S\) の長さを \(s\)\(T\) の長さを \(t\) とし、文字列 \(X\) の先頭 \(i\) 文字のことを \(X[:i]\) と表記します。また、\(T\) に含まれる各文字が \(S\) にもまた含まれていることを仮定します(そうでない場合答えは明らかに \(0\) です)。

\(i=1,2,\dots,t\) の順に以下の値を求めることを考えます。

  • \(\mathrm{iter}_i\)\(g(T[:i],k)\)\(f(S,N)[:j]\) の部分文字列であるような最小の \(j\)

最終的には \(\mathrm{iter}_t\)\(N\times s\) の大小を比較することで答えが求まります。\(\mathrm{iter}_i\) から \(\mathrm{iter}_{i+1}\) を求めるのは \(f(S,N)\)\(\mathrm{iter}_i+1\) 文字目以降で \(T\)\(i+1\) 文字目が \(k\) 回目に現れる場所を求めることと一致するので、本問題は、以下の形式のクエリを \(N\) 回処理する問題に帰着されます。

  • \(\mathrm{query}(a,b,c)\):正整数 \(a,b\)、文字 \(c\) が与えられる。\(f(S,N)\)\(a\) 文字目以降で \(c\)\(b\) 回目に現れるのは何文字目か。

\(S\) の中に \(c\) が現れる回数を \(\mathrm{cnt}_c\) とおきます。\(a\)\(s\) より大きい場合、\(\mathrm{query}(a,b,c)=\mathrm{query}(a-s,b,c)+s\) を用いて \(a\leq s\) の場合に帰着できます。また、\(b\)\(\mathrm{cnt}_c\) より大きい場合、\(\mathrm{query}(a,b,c)=\mathrm{query}(a+s,b-\mathrm{cnt}_c,c)\) を利用して \(b\leq \mathrm{cnt}_c\) の場合に帰着できます。

よって、\(a\leq s,\ b\leq \mathrm{cnt}_c\) の条件のもとでクエリを処理できればよいことがわかりました。これらの条件が満たされるとき \(\mathrm{query}(a,b,c)\) の答えは \(2s\) 以下になるので、各英小文字に対してその文字が \(f(S,2)\) 内で現れる位置のリストを前計算で求めておけば、二分探索などを用いて各クエリを高速に処理することができます。

よって、最初の \(k\) に関する二分探索を含め、全体で \(O(s\sigma+t\log Ns)\)\(O(s+\sigma+t\log s\log Ns)\)\(\sigma\) は文字の種類数)などの計算量で本問題を解くことができます。

実装例 (C++) :

#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const ll inf = 1LL << 60;

int main() {
    ll n;
    string s, t;
    cin >> n >> s >> t;
    int sl = s.size(), tl = t.size();
    vector<vector<int>> pos(26);
    for (int i = 0; i < sl * 2; i++) {
        pos[s[i % sl] - 'a'].push_back(i);
    }
    vector pre(sl + 1, vector<int>(26)); // S の最初の i 文字に j が何回現れるか
    for (int i = 0; i < sl; i++) {
        pre[i + 1] = pre[i];
        ++pre[i + 1][s[i] - 'a'];
    }
    vector<int> cnt(26);
    for (int i = 0; i < 26; i++) cnt[i] = pos[i].size() / 2;
    ll ok = 0, ng = inf;
    auto f = [&](ll x) -> bool {
        ll iter = 0;
        for (int i = 0; i < tl; i++) {
            int c = t[i] - 'a';
            if (!cnt[c]) return false;
            ll r = (x - 1) % cnt[c] + 1;
            ll q = (x - r) / cnt[c];
            if (q > inf / sl) return false;
            iter += sl * q;
            int nx = pos[c][pre[iter % sl][c] + r - 1]; // iter % sl 以降で r 番目に c が現れる場所
            iter += nx + 1 - iter % sl;
            if (iter > n * sl) return false;
        }
        return true;
    };
    while (ng - ok > 1) {
        ll mid = (ok + ng) / 2;
        if (f(mid)) ok = mid;
        else ng = mid;
    }
    cout << ok << endl;
}

posted:
last update: