Song of the Sirens
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Progressive nudges
Open only as much as you need to keep the solve alive.
Think about how the number of occurrences of in relates to the number in . Since , each copy of contributes its own occurrences. What about new ones?
New occurrences of in can only appear at the "junction" — the region where the first , the character , and the second meet. Specifically, these are occurrences that overlap with . This gives , where counts the crossing occurrences.
The junction region is: (last chars of ) + + (first chars of ). Observe that the prefix of equals the prefix of (and transitively of ), and similarly the suffix. So once , these prefix/suffix values stabilize, and depends only on .
After stabilization (level ), you get where is precomputed for each character. Unrolling gives the closed form: where satisfies .
Precompute and (mod ) for all and all 26 characters in . For each query : (1) find stabilization level by building until (this is since sizes double), (2) compute via KMP on , (3) compute by counting in each junction string, (4) evaluate the closed-form in . Total: .
Key Observation
Since , the count of occurrences of in satisfies:
where counts the new occurrences that cross the junction (i.e., they overlap with the inserted character ).
Junction Analysis
The junction region is formed by the last characters of , the character , and the first characters of . This string has length .
Critical insight: Any occurrence of (length ) within a string of length must overlap the center character. So every occurrence found in the junction is genuinely new — none are double-counted from .
Prefix/Suffix Stabilization
Since starts with (and transitively with ), and ends with (and transitively with ), the prefix and suffix of of any fixed length stabilize quickly. Specifically, once for some level , the junction string at every subsequent level is:
So depends only on the character . We precompute for each of the 26 possible characters by running KMP on the junction string.
Closed-Form via Decomposition
For , the recurrence unrolls to:
Grouping by character :
where satisfies the recurrence .
Algorithm
-
Precompute and for all and all 26 characters. Cost: .
-
Per query with :
- Find stabilization level : build explicitly until (at most doublings, total string length ).
- If : answer is .
- Compute via KMP on : .
- Compute for each character via KMP on 26 junction strings: .
- Evaluate the closed-form formula: .
Complexity
- Precomputation:
- Per query: where
- Total: (ignoring the alphabet constant)
This comfortably handles and .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO(const string& name = "") {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#ifdef ZK_LOCAL_RUN
freopen("f.in", "r", stdin);
freopen("f.out", "w", stdout);
#else
if (!name.empty()) {
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
#endif
}
const int MOD = 1e9 + 7;
int kmpCount(const string& text, const string& pat) {
int n = text.size(), m = pat.size();
if (m == 0 || m > n) return 0;
vector<int> fail(m, 0);
for (int i = 1; i < m; i++) {
int j = fail[i - 1];
while (j > 0 && pat[i] != pat[j]) j = fail[j - 1];
if (pat[i] == pat[j]) j++;
fail[i] = j;
}
int cnt = 0, j = 0;
for (int i = 0; i < n; i++) {
while (j > 0 && text[i] != pat[j]) j = fail[j - 1];
if (text[i] == pat[j]) j++;
if (j == m) { cnt++; j = fail[j - 1]; }
}
return cnt;
}
int main() {
setIO();
int n, q;
cin >> n >> q;
string s0, c;
cin >> s0 >> c;
// Precompute pw[k] = 2^k mod MOD
vector<ll> pw(n + 1);
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % MOD;
// T[a][k] = sum_{j=1..k, c[j-1]=='a'+a} 2^{k-j} mod MOD
// Recurrence: T[a][k] = 2*T[a][k-1] + (c[k-1]-'a'==a)
vector<vector<ll>> T(26, vector<ll>(n + 1, 0));
for (int a = 0; a < 26; a++)
for (int k = 1; k <= n; k++)
T[a][k] = (2 * T[a][k - 1] + (c[k - 1] - 'a' == a)) % MOD;
while (q--) {
int w;
string t;
cin >> w >> t;
int m = (int)t.size();
// Find stabilization level L: smallest i with |s_i| >= m-1
// Build s_i explicitly until that happens (or we reach level w)
int L = -1;
string cur = s0;
if ((int)cur.size() >= m - 1) {
L = 0;
} else {
for (int i = 1; i <= w; i++) {
cur = cur + c[i - 1] + cur;
if ((int)cur.size() >= m - 1) {
L = i;
break;
}
}
}
if (L == -1) {
// |s_w| < m, pattern t cannot occur
cout << 0 << "\n";
continue;
}
string& sL = (L == 0) ? s0 : cur;
ll fL = kmpCount(sL, t) % MOD;
if (w == L) {
cout << fL << "\n";
continue;
}
// w > L: compute g[alpha] for each character using the stabilized prefix/suffix
string pref, suf;
if (m >= 2) {
pref = sL.substr(0, m - 1);
suf = sL.substr((int)sL.size() - (m - 1));
}
int g[26] = {};
for (int a = 0; a < 26; a++) {
string junc = suf + char('a' + a) + pref;
g[a] = kmpCount(junc, t);
}
// f[w] = 2^{w-L} * fL + sum_a g[a] * (T[a][w] - 2^{w-L} * T[a][L])
ll ans = pw[w - L] * fL % MOD;
for (int a = 0; a < 26; a++) {
if (!g[a]) continue;
ll d = (T[a][w] - pw[w - L] * T[a][L] % MOD + MOD) % MOD;
ans = (ans + (ll)g[a] % MOD * d) % MOD;
}
cout << ans << "\n";
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO(const string& name = "") {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#ifdef ZK_LOCAL_RUN
freopen("f.in", "r", stdin);
freopen("f.out", "w", stdout);
#else
if (!name.empty()) {
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
#endif
}
const int MOD = 1e9 + 7;
int kmpCount(const string& text, const string& pat) {
int n = text.size(), m = pat.size();
if (m == 0 || m > n) return 0;
vector<int> fail(m, 0);
for (int i = 1; i < m; i++) {
int j = fail[i - 1];
while (j > 0 && pat[i] != pat[j]) j = fail[j - 1];
if (pat[i] == pat[j]) j++;
fail[i] = j;
}
int cnt = 0, j = 0;
for (int i = 0; i < n; i++) {
while (j > 0 && text[i] != pat[j]) j = fail[j - 1];
if (text[i] == pat[j]) j++;
if (j == m) { cnt++; j = fail[j - 1]; }
}
return cnt;
}
int main() {
setIO();
int n, q;
cin >> n >> q;
string s0, c;
cin >> s0 >> c;
// Precompute pw[k] = 2^k mod MOD
vector<ll> pw(n + 1);
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % MOD;
// T[a][k] = sum_{j=1..k, c[j-1]=='a'+a} 2^{k-j} mod MOD
// Recurrence: T[a][k] = 2*T[a][k-1] + (c[k-1]-'a'==a)
vector<vector<ll>> T(26, vector<ll>(n + 1, 0));
for (int a = 0; a < 26; a++)
for (int k = 1; k <= n; k++)
T[a][k] = (2 * T[a][k - 1] + (c[k - 1] - 'a' == a)) % MOD;
while (q--) {
int w;
string t;
cin >> w >> t;
int m = (int)t.size();
// Find stabilization level L: smallest i with |s_i| >= m-1
// Build s_i explicitly until that happens (or we reach level w)
int L = -1;
string cur = s0;
if ((int)cur.size() >= m - 1) {
L = 0;
} else {
for (int i = 1; i <= w; i++) {
cur = cur + c[i - 1] + cur;
if ((int)cur.size() >= m - 1) {
L = i;
break;
}
}
}
if (L == -1) {
// |s_w| < m, pattern t cannot occur
cout << 0 << "\n";
continue;
}
string& sL = (L == 0) ? s0 : cur;
ll fL = kmpCount(sL, t) % MOD;
if (w == L) {
cout << fL << "\n";
continue;
}
// w > L: compute g[alpha] for each character using the stabilized prefix/suffix
string pref, suf;
if (m >= 2) {
pref = sL.substr(0, m - 1);
suf = sL.substr((int)sL.size() - (m - 1));
}
int g[26] = {};
for (int a = 0; a < 26; a++) {
string junc = suf + char('a' + a) + pref;
g[a] = kmpCount(junc, t);
}
// f[w] = 2^{w-L} * fL + sum_a g[a] * (T[a][w] - 2^{w-L} * T[a][L])
ll ans = pw[w - L] * fL % MOD;
for (int a = 0; a < 26; a++) {
if (!g[a]) continue;
ll d = (T[a][w] - pw[w - L] * T[a][L] % MOD + MOD) % MOD;
ans = (ans + (ll)g[a] % MOD * d) % MOD;
}
cout << ans << "\n";
}
return 0;
}