题意:
给出两个串 s、t,求公共本质不同子串的个数。
思路:
对 s 建后缀自动机,将 t 放到上面跑,联系之前学过的用 SAM 求两个串的公共子串,对于 SAM 中的每个状态 p 求出在字符串 t 和 状态 p 中都出现过的最长子串长度 now[p](求完还需要 dfs 拓扑更新一遍)
类比于 P4070 [SDOI2016]生成魔咒,不过我们只关注 SAM 中两个串的 “公共节点”,当 dfs 遍历到某个 “公共节点”,我们加上对应的贡献,具体细节详见代码。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = N << 1;
int ch[M][26], len[M], fa[M], np = 1, tot = 1;
vector<int> g[M];
char s[N], t[N];
int now[M]; //表示当前在 t 和 状态 p 中都出现过的最长子串长度,s 的子串都被划分到了每一个状态 p 中
long long ans; //记录答案
void extend(int c)
{
int p = np; np = ++tot;
len[np] = len[p] + 1;
while (p && !ch[p][c]) {
ch[p][c] = np;
p = fa[p];
}
if (!p) {
fa[np] = 1;
}
else {
int q = ch[p][c];
if (len[q] == len[p] + 1) {
fa[np] = q;
}
else {
int nq = ++tot;
len[nq] = len[p] + 1;
fa[nq] = fa[q], fa[q] = fa[np] = nq;
while (p && ch[p][c] == q) {
ch[p][c] = nq;
p = fa[p];
}
memcpy(ch[nq], ch[q], sizeof ch[q]);
}
}
}
void dfs(int u)
{
for (auto son : g[u]) {
dfs(son);
now[u] = max(now[u], now[son]); //拓扑更新
}
if (now[u]) { //只加 now[u] 不为 0 的,如果为 0 表示这个点的所有子串都不是两者公共的子串
//原串建成的 SAM 中的节点 u 表示的最大子串长度 len[u] 和 now[u] 大小关系不确定,我们应当取 min(len[u], now[u]),保证节点 u 能够包含该长度子串
ans += min(len[u], now[u]) - len[fa[u]]; //加上对答案造成的贡献
}
}
signed main()
{
scanf("%s%s", s, t);
for (int i = 0; s[i]; ++i) {
extend(s[i] - 'a');
}
for (int i = 2; i <= tot; ++i) {
g[fa[i]].emplace_back(i);
}
//跟之前求两串公共子串的步骤一样
int p = 1, leng = 0;
for (int i = 0; t[i]; ++i) {
int c = t[i] - 'a';
while (p > 1 && !ch[p][c]) {
p = fa[p];
leng = len[p];
}
if (ch[p][c]) {
++leng;
p = ch[p][c];
}
now[p] = max(now[p], leng);
}
dfs(1); //拓扑更新一遍 now 数组
printf("%lld\n", ans);
return 0;
}