题意:
给你一个字符串,有若干次询问,每次询问给你一个串和一对
(
l
,
r
)
(l,r)
(l,r),问你询问串有多少个本质不同的子串没有在原串
[
l
,
r
]
[l,r]
[l,r]区间内出现过。总询问串长
<
=
1
e
6
<=1e6
<=1e6,单个串和原串长
<
=
5
e
5
<=5e5
<=5e5
题解:
好像我NOI网上同步赛这个题爆零了,现在再来做还是不会做,还是看了题解,我好菜啊QAQ。不知道为什么BZOJ上是权限题,想不明白BZOJ有什么理由让NOI题变成权限题,所以我没在BZOJ上交,所以不保证交上去会不会出问题,但是在洛谷上是过了的。加上是为了方便别人搜索到题解吧。
还是像之前说的那个样子,本质不同的子串问题还是考虑SA或者SAM,但是这个数据范围应该SA比较有可能被卡常的样子,所以尽可能地用SAM来做。
首先我们考虑如果 [ l , r ] = [ 1 , n ] [l,r]=[1,n] [l,r]=[1,n],也就是就问你询问串在原串中没有出现过的本质不同的子串的个数怎么来做。首先本质不同的子串个数可以用SAM求出,不会的话可以自行翻阅我之前的博客。但是我们这里还有了一个不能在原串中出现的限制。我们考虑把询问串在原串的SAM上进行匹配,如果适配了就跳parent树的父节点,这样我们可以找出询问串的每一个前缀在原串已经出现的最长的后缀的长度。对于新建的节点我们记录一下它right集合中编号最小的点,我们用最小的那个点的限制来表示它的限制,因为parent树上同一个点的所有right集合里的串是相同的,它们在原串中出现的最长后缀也是相同的。这样我们对询问串也建一个SAM,算本质不同的子串的时候要减的是它父节点的最大长度和这个最长出现后缀长度的max。
那么我们考虑询问区间不再是原串整个串的时候该怎么做。我们在原串的SAM上匹配询问串的时候,如果能跳到那个子节点的条件不仅仅是它要在整个串里出现过,而且还要在询问串的子串出现过。假设我们现在已经匹配的长度是 l e n len len,询问的原串区间是 [ l , r ] [l,r] [l,r],那么我们就要看下一个跳到的点的right集合里是否有在 [ l + l e n , r ] [l+len,r] [l+len,r]出现过的串。这个东西我们用一个线段树合并来维护,在每个前缀串的结束节点对应的线段树的根把这个点加进去,然后在parent树上进行线段树合并,这样在跳的时候就在对应的线段树上询问一下区间和是不是大于0就可以。这样对于询问不是原串整个串的情况,我们也能求出询问串的每一个前缀在这种情况下的最长出现过的前缀了。然后可以用上面说的方法求出最终的答案。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,len[1000010],fa[1000010],cnt=1,rt=1,lst=1,ch[1000010][26];
int len1[1000010],fa1[1000010],cnt1,rt1,lst1,ch1[1000010][26],tag[1000010];
char s[500010];
int num,hed[2000010],root[2000010],lim[2000010];
long long ans;
struct node
{
int to,next;
}a[2000010];
struct tree
{
int l,r,sz;
}tr[50000010];
inline void update(int &rt,int l,int r,int x)
{
if(!rt)
rt=++cnt1;
tr[rt].sz++;
if(l==r)
return;
int mid=(l+r)>>1;
if(x<=mid)
update(tr[rt].l,l,mid,x);
else
update(tr[rt].r,mid+1,r,x);
}
inline void insert(int x,int y)
{
int cur=++cnt,pre=lst;
lst=cur;
len[cur]=len[pre]+1;
for(;pre&&!ch[pre][x];pre=fa[pre])
ch[pre][x]=cur;
if(!pre)
fa[cur]=rt;
else
{
int ji=ch[pre][x];
if(len[ji]==len[pre]+1)
fa[cur]=ji;
else
{
int gg=++cnt;
memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
len[gg]=len[pre]+1;
fa[gg]=fa[ji];
fa[ji]=fa[cur]=gg;
for(;pre&&ch[pre][x]==ji;pre=fa[pre])
ch[pre][x]=gg;
}
}
update(root[cur],1,n,y);
}
inline void add(int from,int to)
{
a[++num].to=to;
a[num].next=hed[from];
hed[from]=num;
}
inline void merge(int &x,int l,int r)
{
if(!l||!r)
{
x=l+r;
return;
}
x=++cnt1;
tr[x].sz=tr[l].sz+tr[r].sz;
merge(tr[x].l,tr[l].l,tr[r].l);
merge(tr[x].r,tr[l].r,tr[r].r);
}
inline void dfs(int x)
{
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
dfs(y);
merge(root[x],root[x],root[y]);
}
}
inline void insert1(int x,int y)
{
int cur=++cnt1,pre=lst1;
lst1=cur;
len1[cur]=len1[pre]+1;
tag[cur]=y;
for(;pre&&!ch1[pre][x];pre=fa1[pre])
ch1[pre][x]=cur;
if(!pre)
fa1[cur]=rt1;
else
{
int ji=ch1[pre][x];
if(len1[ji]==len1[pre]+1)
fa1[cur]=ji;
else
{
int gg=++cnt1;
memcpy(ch1[gg],ch1[ji],sizeof(ch1[ji]));
len1[gg]=len1[pre]+1;
fa1[gg]=fa1[ji];
tag[gg]=tag[ji];
fa1[ji]=fa1[cur]=gg;
for(;pre&&ch1[pre][x]==ji;pre=fa1[pre])
ch1[pre][x]=gg;
}
}
}
inline int query(int rt,int l,int r,int le,int ri)
{
if(!rt||le>ri)
return 0;
if(le<=l&&r<=ri)
return tr[rt].sz;
int mid=(l+r)>>1,res=0;
if(le<=mid)
res+=query(tr[rt].l,l,mid,le,ri);
if(mid+1<=ri)
res+=query(tr[rt].r,mid+1,r,le,ri);
return res;
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)
insert(s[i]-'a',i);
for(int i=1;i<=cnt;++i)
add(fa[i],i);
num=0;
dfs(1);
for(int i=1;i<=n;++i)
s[i]=0;
scanf("%d",&m);
for(int k=1;k<=m;++k)
{
scanf("%s",s+1);
int ln=0;
ln=strlen(s+1);
int l,r;
scanf("%d%d",&l,&r);
cnt1=1;
rt1=1;
lst1=1;
ans=0;
for(int i=1;i<=ln;++i)
insert1(s[i]-'a',i);
int cur=1,ji=0;
for(int i=1;i<=ln;++i)
{
int x=s[i]-'a';
while(1)
{
if(ch[cur][x]&&query(root[ch[cur][x]],1,n,l+ji,r))
{
cur=ch[cur][x];
++ji;
break;
}
if(!ji)
break;
--ji;
if(ji==len[fa[cur]])
cur=fa[cur];
}
lim[i]=ji;
}
for(int i=2;i<=cnt1;++i)
ans+=max(0,len1[i]-max(len1[fa1[i]],lim[tag[i]]));
printf("%lld\n",ans);
for(int i=1;i<=cnt1;++i)
{
fa1[i]=0;
len1[i]=0;
tag[i]=0;
lim[i]=0;
for(int j=0;j<=25;++j)
ch1[i][j]=0;
}
for(int i=1;i<=ln;++i)
s[i]=0;
}
return 0;
}