Solution
We need to count distinct nonempty strings that occur at least three times in both and , such that the positions of all their occurrences differ by the same shift.
Let P_x and Q_x be the sets of ending positions of occurrences of in and , respectively.
The string is synchronous if and only if
and the two sets become identical after subtracting their minimum elements:
We will use a suffix automaton and polynomial hashing to check these conditions efficiently.
Suffix automaton
Build a suffix automaton for the string s+\#+t, where is a separator that does not occur in either input string.
Recall that each state of a suffix automaton represents all substring lengths in the interval
All substrings represented by the same state have exactly the same sets of ending positions.
Therefore, for every state , the number of its occurrences in and , as well as the relative positions of these occurrences, are identical for all represented substrings.
This means that either all substrings represented by a state are synchronous, or none of them are.
If a state satisfies the required conditions, its contribution to the answer is
States corresponding to substrings containing the separator cannot have occurrences in both original strings, so they will not contribute to the answer.
Checking the occurrence positions
For every state , maintain the following information separately for and :
cnt_s, cnt_t — the numbers of occurrences;
mn_s, mn_t — the minimum ending positions;
H_s, H_t — polynomial hashes of the sets of ending positions.
For a fixed base and modulus , define
We want to compare the occurrence positions after subtracting their minimum elements.
Instead of explicitly normalizing every position, we can compare
Indeed, if the two sets differ only by a constant shift, multiplying their hashes by these powers makes them equal.
To make the probability of hash collisions negligible, we perform this comparison using two different moduli.
Thus, a state is accepted if
and both hash comparisons succeed.
Calculating the information
While constructing the suffix automaton, every position of or corresponds to a terminal state.
For such a state, initially assign an occurrence count of , its ending position, and the corresponding power of to the appropriate string.
All other values are initially zero. Cloned states do not receive additional occurrences.
After constructing the automaton, propagate the information along suffix links from children to parents.
When merging the information of two states, we add their occurrence counts and hashes, and take the minimum of their ending positions.
This gives the required information for every state in linear time.
Finally, iterate over all states. For every state satisfying the conditions, add
to the answer.
Complexity Analysis
A suffix automaton for a string of length |s|+|t|+1 contains O(|s|+|t|) states.
Constructing the automaton, propagating the information along suffix links, and checking every state all take linear time.
Therefore, the total time complexity is O(|s|+|t|), and the space complexity is O(|s|+|t|).
Implementation
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M1 = 1000000007;
const int M2 = 1000000009;
const int B = 911382323;
struct State {
int to[27] = {};
int link = -1, len = 0;
int cs = 0, ct = 0;
int ms = INT_MAX, mt = INT_MAX;
int hs1 = 0, ht1 = 0, hs2 = 0, ht2 = 0;
};
void solve() {
string s, t;
cin >> s >> t;
int n = max(s.size(), t.size());
vector<int> pw1(n + 1, 1), pw2(n + 1, 1);
for (int i = 1; i <= n; i++) {
pw1[i] = 1LL * pw1[i - 1] * B % M1;
pw2[i] = 1LL * pw2[i - 1] * B % M2;
}
vector<State> st;
st.reserve(2 * (s.size() + t.size() + 1) + 1);
st.emplace_back();
int last = 0;
auto extend = [&](int c) {
int cur = st.size();
st.emplace_back();
st[cur].len = st[last].len + 1;
int p = last;
while (p != -1 && st[p].to[c] == 0) {
st[p].to[c] = cur;
p = st[p].link;
}
if (p == -1) {
st[cur].link = 0;
} else {
int q = st[p].to[c];
if (st[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
State cl = st[q];
cl.len = st[p].len + 1;
cl.cs = cl.ct = 0;
cl.hs1 = cl.ht1 = cl.hs2 = cl.ht2 = 0;
cl.ms = cl.mt = INT_MAX;
int clone = st.size();
st.push_back(cl);
while (p != -1 && st[p].to[c] == q) {
st[p].to[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
return cur;
};
for (int i = 0; i < (int)s.size(); i++) {
int v = extend(s[i] - 'a');
st[v].cs = 1;
st[v].ms = i;
st[v].hs1 = pw1[i];
st[v].hs2 = pw2[i];
}
extend(26);
for (int i = 0; i < (int)t.size(); i++) {
int v = extend(t[i] - 'a');
st[v].ct = 1;
st[v].mt = i;
st[v].ht1 = pw1[i];
st[v].ht2 = pw2[i];
}
int sz = st.size();
vector<int> deg(sz);
for (int v = 1; v < sz; v++) {
deg[st[v].link]++;
}
queue<int> q;
for (int v = 1; v < sz; v++) {
if (deg[v] == 0) q.push(v);
}
while (!q.empty()) {
int v = q.front();
q.pop();
int p = st[v].link;
st[p].cs += st[v].cs;
st[p].ct += st[v].ct;
st[p].ms = min(st[p].ms, st[v].ms);
st[p].mt = min(st[p].mt, st[v].mt);
st[p].hs1 = (st[p].hs1 + st[v].hs1) % M1;
st[p].ht1 = (st[p].ht1 + st[v].ht1) % M1;
st[p].hs2 = (st[p].hs2 + st[v].hs2) % M2;
st[p].ht2 = (st[p].ht2 + st[v].ht2) % M2;
if (--deg[p] == 0 && p != 0) {
q.push(p);
}
}
ll ans = 0;
for (int v = 1; v < sz; v++) {
State &x = st[v];
if (x.cs < 3 || x.cs != x.ct) continue;
if (1LL * x.hs1 * pw1[x.mt] % M1 !=
1LL * x.ht1 * pw1[x.ms] % M1) continue;
if (1LL * x.hs2 * pw2[x.mt] % M2 !=
1LL * x.ht2 * pw2[x.ms] % M2) continue;
ans += x.len - st[x.link].len;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
}