题目
题目链接:https://www.luogu.com.cn/problem/P4770
小 A 被选为了 ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。
由于 ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。
由于一些特殊的原因,小 A 不知道 ION2017 每道题的名字,但是他通过一些特殊手段得到了 ION2017 的命名串,现在小 A 有 (Q) 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。
由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串,详细可见输入格式。
(|S|,|T|leq 5 imes 10^5;sum |T|leq 10^6;Qleq 10^5)。
思路
SAM Visualizer 简直是 debug 神器!!!!11
设 (n=|S|,m=|T|)。
首先考虑 (l=1,r=n) 的部分分。也就是给定 (S,T),询问有多少个 (T) 本质不同的子串没有在 (S) 中出现过。
显然只需要求出有多少个 (T) 本质不同的子串在 (S) 中出现过,然后用本质不同子串数量减一下即可。
考虑枚举 (T) 的每一个位置 (i),不难发现以 (i) 结尾,且在 (S) 中出现过的 (T) 的子串一定是右端点为 (i) 的一段后缀。我们只需要求出每一个后缀的长度加起来即可。当然还需要保证本质不同。
对 (S) 和 (T) 分别建一个 SAM,然后考虑将 (T) 在 (S) 的后缀自动机上跑匹配。我们知道 parent 树上,一个节点 (x) 的父亲表示 (x) 所代表等价类最短串删去第一个字符后所代表的等价类。这玩意和 AC 自动机的 fail 十分相似。
假设我们枚举到 (T) 的第 (i) 位,现在在 (S) 的后缀自动机上匹配到的点为 (x),那么我们可以不断跳 (x) 在 parent 树上的父亲,直到 (x) 在后缀自动机上有 (T_i) 这条出边为止,那么直接把 (x) 赋值为出边所连接的点编号即可。显然这一个点就是与 (T) 在 (i) 处结尾的子串匹配的等价类。匹配的长度为 ( ext{len}_{ ext{fa}[x]}+1)。
那么我们照样在 (T) 的后缀自动机上找到 (T[1:i]) 所在的等价类,然后把匹配长度记录下来,最后 topsort 一次计算答案即可。
时间复杂度是喜闻乐见的 (O(n+m)) 的。
接下来考虑回原题。我们肯定无法快速得到 (S) 的一个子串的 SAM,但是观察我们在 (S) 的 SAM 上的操作只有以下三种:
- 跳 parent 树匹配。
- 判断一个点 (x) 是否有字符 (c) 的出边。
- 用点 ( ext{len}_x) 更新 (T) 的后缀自动机的答案。
其中跳 parent 树是不受区间影响的,复杂度只和 (sum |T|) 有关。
判断一个点 (x) 是否有字符 (c) 的出边则增加一个一个要求:去往的点的 ( ext{endpos}cap [l,r]
eq emptyset)。这个直接可持久化线段树合并维护 ( ext{endpos}) 即可。
用点 ( ext{len}_x) 更新 (T) 的后缀自动机的答案,因为有了区间限制,我们可能取到的串有一部分前缀会超出区间范围。但是也比较容易处理。我们在线段树上维护区间最大值,要求匹配串长度时就查询 ([l,r]) 的最大值,因为在一个等价类中,我们要保证能往前选择更多,在右端点肯定不超过范围的前提下,右端点越后能去到的长度越长。具体的,记 (p) 为 ( ext{endpos}cap [l,r]) 集合中的最大值,能匹配的长度则为 (min( ext{len}_{ ext{fa}[x]+1,p-l+1}))。
有一点需要注意的是,取了 (min) 之后,能匹配的长度可能就比 (x) 等价类中最短子串的长度更短了,此时我们依然需要继续跳 parent 树,因为 ( ext{fa}[x]) 可能会有更后的 ( ext{endpos})。
那么我们就解决了区间的问题。时间复杂度 (O((n+sum m)log n))。
代码
因为一个傻逼错误调了一个下午。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010,LG=20;
int n,m,Q,leng,rt[N];
char s[N],t[N];
ll ans;
struct SegTree
{
int tot,lc[N*LG*4],rc[N*LG*4],maxp[N*LG*4];
int update(int x,int l,int r,int k,int pos)
{
if (!x) x=++tot;
if (l==r) { maxp[x]=max(maxp[x],pos); return x; }
int mid=(l+r)>>1;
if (k<=mid) lc[x]=update(lc[x],l,mid,k,pos);
else rc[x]=update(rc[x],mid+1,r,k,pos);
maxp[x]=max(maxp[lc[x]],maxp[rc[x]]);
return x;
}
int merge(int x,int y)
{
if (!x || !y) return x|y;
int p=++tot;
maxp[p]=max(maxp[x],maxp[y]);
lc[p]=merge(lc[x],lc[y]);
rc[p]=merge(rc[x],rc[y]);
return p;
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql<=l && qr>=r) return maxp[x];
int mid=(l+r)>>1,res=0;
if (ql<=mid) res=max(res,query(lc[x],l,mid,ql,qr));
if (qr>mid) res=max(res,query(rc[x],mid+1,r,ql,qr));
return res;
}
}seg;
struct SAM
{
int last,tot,len[N],fa[N],ch[N][26],res[N],wyctql[N],xjqtql[N];
void init()
{
for (int i=0;i<=tot;i++)
{
len[i]=fa[i]=wyctql[i]=xjqtql[i]=res[i]=0;
for (int j=0;j<26;j++) ch[i][j]=0;
}
last=tot=1;
}
void ins(int c,int k)
{
int p=last,np=++tot;
last=np; len[np]=len[p]+1;
if (k!=-1) rt[np]=seg.update(rt[np],1,n,k,k);
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
fa[nq]=fa[q]; len[nq]=len[p]+1;
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void topsort(bool type)
{
for (int i=1;i<=tot;i++) xjqtql[len[i]]++;
for (int i=1;i<=tot;i++) xjqtql[i]+=xjqtql[i-1];
for (int i=1;i<=tot;i++) wyctql[xjqtql[len[i]]--]=i;
for (int i=tot;i>=1;i--)
{
int x=wyctql[i];
if (!type) rt[fa[x]]=seg.merge(rt[fa[x]],rt[x]);
else
{
res[fa[x]]=max(res[fa[x]],res[x]);
res[x]=min(res[x],len[x]);
if (x>1) ans+=len[x]-max(len[fa[x]],res[x]);
}
}
}
int find(int x,int c,int l,int r)
{
for (;x;x=fa[x],leng=len[x])
{
if (!ch[x][c]) continue;
int p=seg.query(rt[ch[x][c]],1,n,l,r);
if (p && p-l+1>len[fa[ch[x][c]]])
{
leng=min(leng+1,p-l+1);
return ch[x][c];
}
}
return 1;
}
}sam1,sam2;
void solve(int l,int r)
{
leng=0;
for (int i=1,p1=1,p2=1;i<=m;i++)
{
p1=sam1.find(p1,t[i]-'a',l,r);
p2=sam2.ch[p2][t[i]-'a'];
sam2.res[p2]=leng;
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
sam1.init();
for (int i=1;i<=n;i++)
sam1.ins(s[i]-'a',i);
sam1.topsort(0);
scanf("%d",&Q);
while (Q--)
{
int l,r; ans=0;
scanf("%s%d%d",t+1,&l,&r);
m=strlen(t+1);
sam2.init();
for (int i=1;i<=m;i++)
sam2.ins(t[i]-'a',-1);
solve(l,r);
sam2.topsort(1);
printf("%lld
",ans);
}
return 0;
}