【题目】
原题地址
给定一个字符串
S
S
S,多组询问给定字符串
T
T
T以及两个数字
l
,
r
l,r
l,r。求
S
[
l
.
.
r
]
S[l..r]
S[l..r]中有多少个子串
x
x
x满足:
x
x
x的任意一个子串没有在
T
T
T中出现过。
∣
S
∣
,
∑
∣
T
∣
≤
5
×
1
0
5
|S|,\sum |T| \leq 5\times 10^5
∣S∣,∑∣T∣≤5×105
【解题思路】
加深对
SAM
\text{SAM}
SAM的理解。
既然是字符串题,我们首先对
S
S
S和
T
T
T分别建
SAM
\text{SAM}
SAM。
考虑
l
=
1
,
r
=
∣
S
∣
l=1,r=|S|
l=1,r=∣S∣的情况。
设
l
i
m
i
lim_i
limi为
T
[
1
,
i
]
T[1,i]
T[1,i]能匹配
S
S
S的最长后缀为
T
[
i
−
l
i
m
i
+
1
,
i
]
T[i-lim_i+1,i]
T[i−limi+1,i](若
l
i
m
i
=
0
lim_i=0
limi=0则
T
i
T_i
Ti没有在
S
S
S中出现过)。那么这个我们在
SAM
\text{SAM}
SAM上一路往下跑,匹配不了就往
f
a
fa
fa跳,这样就可以简单处理出来,有点类似双指针。
设
SAM
\text{SAM}
SAM中节点
i
i
i的
r
i
g
h
t
right
right集合包含的字符串最大长度为
m
x
i
mx_i
mxi,字符串第一次出现的位置为
e
n
d
i
end_i
endi(这个节点
r
i
g
h
t
right
right集中的每个串
e
n
d
i
end_i
endi都是一样的,因为是后缀包含关系,终点等价)。我们枚举
T
T
T中的每个节点,考虑每条
i
i
i到
f
a
i
fa_i
fai边的贡献,那么:
a
n
s
=
∑
i
=
2
c
n
t
m
a
x
(
0
,
m
x
i
−
m
a
x
(
m
x
f
a
i
,
l
i
m
e
n
d
i
)
)
ans=\sum_{i=2}^{cnt} max(0,mx_i-max(mx_{fa_i},lim_{end_i}))
ans=i=2∑cntmax(0,mxi−max(mxfai,limendi))
这个式子的意思就是对于每个节点,不属于
S
S
S的子串的总个数为当前节点所代表的集合字符串个数减去与
S
S
S有匹配的字符串个数。
于是现在实际上总的问题就是求这个
l
i
m
i
lim_i
limi。
我们可以对于
S
S
S的
SAM
\text{SAM}
SAM每个节点按
m
x
i
mx_i
mxi排序,每个节点建线段树表示出现位置,做线段树合并。那么每次我们求
l
i
m
i
lim_i
limi,往下扩展的时候,我么只需要判断这个节点线段树中
[
l
+
l
e
n
,
r
]
[l+len,r]
[l+len,r]是否出现过即可。其中
l
e
n
len
len表示当前匹配了
T
[
i
−
l
e
n
+
1
,
i
]
T[i-len+1,i]
T[i−len+1,i],出现了即表示字符串出现在
S
[
l
,
r
]
S[l,r]
S[l,r]内。
复杂度 O ( P log P ) O(P\log P) O(PlogP),我们取 P P P是 1 0 6 10^6 106级别吧。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10,M=N*20;
int rt[N],b[N],c[N],lim[N];
char s[N];
ll ans;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct Segment
{
int sz,ls[M],rs[M];
void insert(int &x,int l,int r,int p)
{
if(!x) x=++sz;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) insert(ls[x],l,mid,p);
else insert(rs[x],mid+1,r,p);
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
int z=++sz;
ls[z]=merge(ls[x],ls[y]);rs[z]=merge(rs[x],rs[y]);
return z;
}
bool query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return 1;
int mid=(l+r)>>1;bool res=0;
if(L<=mid) res|=query(ls[x],l,mid,L,R);
if(R>mid) res|=query(rs[x],mid+1,r,L,R);
return res;
}
}tr;
struct SAM
{
int sz,tot,las,n,mx[N],fa[N],pos[N],ch[N][26];
void extend(int x,int c)
{
int p,np,q,nq;
p=las;las=np=++sz;mx[np]=mx[p]+1;pos[np]=c;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;pos[nq]=pos[q];
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
}
void reset(int l)
{
memset(ch,0,(sz+2)*sizeof(ch[0]));
sz=las=1;n=l;
}
void buildS(char *s,int l)
{
reset(l);
for(int i=1;i<=n;++i) extend(s[i]-'a',i),tr.insert(rt[las],1,n,i);
for(int i=1;i<=sz;++i) ++b[mx[i]];
for(int i=1;i<=n;++i) b[i]+=b[i-1];
for(int i=1;i<=sz;++i) c[b[mx[i]]--]=i;
for(int i=sz,x=c[i];i>1;x=c[--i]) rt[fa[x]]=tr.merge(rt[x],rt[fa[x]]);
}
void buildT(char *s,int l)
{
reset(l);
for(int i=1;i<=n;++i) extend(s[i]-'a',i);
}
}S,T;
void solve()
{
scanf("%s",s+1);
int l=read(),r=read(),len=strlen(s+1);
T.buildT(s,len);memset(lim,0,(len+2)*4);
for(int i=1,now=0,p=1;i<=len;lim[i++]=now)
{
int x=s[i]-'a';
for(;;)
{
if(!tr.query(rt[S.ch[p][x]],1,S.n,l+now,r))
{
if(!now) break; --now;
if(now==S.mx[S.fa[p]]) p=S.fa[p];
}
else {++now;p=S.ch[p][x];break;}
}
}
ll ans=0;
for(int i=2;i<=T.sz;++i)
ans+=max(0,T.mx[i]-max(T.mx[T.fa[i]],lim[T.pos[i]]));
printf("%lld\n",ans);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("LGP4770.in","r",stdin);
freopen("LGP4770.out","w",stdout);
#endif
scanf("%s",s+1);int len=strlen(s+1);
S.buildS(s,len);
for(int T=read();T;--T) solve();
return 0;
}
【总结】
套路啊套路,本质啊本质。