给你一个字符串 S,然后每次询问给你一个字符串 T 和 l,r。
要你求 T 中有多少个子串不是 S[l]~S[r] 组成的字符串的子串。
思路
首先我们看到很多的部分分是 l=1,r=|S|。
那我们先想想怎么搞这个,也就是求 T 中有多少个子串不是 S 的子串。
首先考虑反过来求有多少个串同时是 S,T 的子串。
然后我们考虑把 T 在 S 的 SAM 上面跑,然后每个位置有匹配长度 len,然后对于 T 的 SAM 的每个点我们记录一个 pli 表示这个点是字符串哪个前缀跑出来的点,然后跑到这个位置的 len 就是能匹配的长度 ansi。
然后答案就是枚举 T 的每个点(1 号点除外),贡献是 max(0,leni−max(lenfai,anspli))(这个 max(0) 是防止 anspli 太大)
那复杂度为 O(|T|) 可以接受。
接着考虑 l,r 随便的情况。
那我们考虑会有什么问题,就是求这个 ansi 的时候,你跑出来的点不一定行。
你要这个点是在 Sl∼r 的 SAM 上才可以,亦或者说要这个点的 endpos 类里面有 l∼r 的信息。
那要怎么判呢,我们考虑维护 S 的 SAM 中一个点的 endpos。
怎么维护呢,我们可以用动态开点线段树,然后父亲的 endpos 是自己并上儿子的 endpos,可以用线段树合并来解决。
代码
#include<cstdio>#include<cstring>#include<iostream>#define ll long longusingnamespace std;
constint N = 5e5 + 10;
char s[N], t[N];
int n, m, q, L, R, rt[N << 1], ans[N << 1];
structXD_tree {
int tot, val[N << 6], ls[N << 6], rs[N << 6];
intinsert(int x, int l, int r, int pl){
int now = ++tot; val[now] = val[x]; ls[now] = ls[x]; rs[now] = rs[x];
val[now] = max(val[now], pl);
if (l == r) return now;
int mid = (l + r) >> 1;
if (pl <= mid) ls[now] = insert(ls[now], l, mid, pl);
else rs[now] = insert(rs[now], mid + 1, r, pl);
return now;
}
intquery(int now, int l, int r, int L, int R){
if (!now) return0;
if (L <= l && r <= R) return val[now];
int mid = (l + r) >> 1, re = 0;
if (L <= mid) re = max(re, query(ls[now], l, mid, L, R));
if (mid < R) re = max(re, query(rs[now], mid + 1, r, L, R));
return re;
}
intmerge(int x, int y){
if (!x || !y) return x + y;
int re = ++tot; val[re] = max(val[x], val[y]);
ls[re] = merge(ls[x], ls[y]); rs[re] = merge(rs[x], rs[y]);
return re;
}
}TT;
structSAM {
int lst, tot;
structnode {
int son[26], len, fa, pl;
}d[N << 1];
voidInit(){
lst = tot = 1;
memset(d[1].son, 0, sizeof(d[1].son));
}
intinsert(char c, int pla){
int p = lst, np = ++tot; lst = np;
memset(d[np].son, 0, sizeof(d[np].son));
d[np].len = d[p].len + 1; d[np].pl = pla;
for (; p && !d[p].son[c]; p = d[p].fa) d[p].son[c] = np;
if (!p) d[np].fa = 1;
else {
int q = d[p].son[c];
if (d[q].len == d[p].len + 1) d[np].fa = q;
else {
int nq = ++tot; d[nq] = d[q];
d[nq].len = d[p].len + 1;
d[np].fa = d[q].fa = nq;
for (; p && d[p].son[c] == q; p = d[p].fa) d[p].son[c] = nq;
}
}
return lst;
}
voidbuild(){
staticint tmp[N], xl[N << 1];
for (int i = 1; i <= n; i++) tmp[i] = 0;
for (int i = 1; i <= tot; i++) tmp[d[i].len]++;
for (int i = 1; i <= n; i++) tmp[i] += tmp[i - 1];
for (int i = 1; i <= tot; i++) xl[tmp[d[i].len]--] = i;
for (int i = tot; i >= 1; i--) {//线段树合并来求SAM每个点的endposint x = xl[i];
rt[d[x].fa] = TT.merge(rt[d[x].fa], rt[x]);
}
}
voidRun(int &now, int &sz, int c){
while (now) {
if (d[now].son[c]) {
int x = TT.query(rt[d[now].son[c]], 1, n, 1, R) - L + 1;
if (d[d[now].fa].len < x) {//不仅要跳到有这个儿子,还需要这个儿子的位置中有 L~R(前面这个比较的要小于这样减的时候【DP】才会是正的) now = d[now].son[c]; sz = min(sz + 1, x);
return ;
}
}
now = d[now].fa; sz = d[now].len;
}
now = 1; sz = 0;
}
ll work(){
ll re = 0;
for (int i = 2; i <= tot; i++)
re += max(d[i].len - max(d[d[i].fa].len, ans[d[i].pl]), 0);
return re;
}
}S, T;
intmain(){
scanf("%s", s + 1); n = strlen(s + 1);
S.Init();
for (int i = 1; i <= n; i++) {
int pl = S.insert(s[i] - 'a', 0);
rt[pl] = TT.insert(rt[pl], 1, n, i);
}
S.build();
scanf("%d", &q);
while (q--) {
scanf("%s", t + 1); m = strlen(t + 1);
scanf("%d %d", &L, &R);
T.Init();
for (int i = 1; i <= m; i++)
T.insert(t[i] - 'a', i);
int now = 1, sz = 0;
for (int i = 1; i <= m; i++) {
S.Run(now, sz, t[i] - 'a');
ans[i] = sz;
}
printf("%lld\n", T.work());
}
return0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律