HDU 4622(后缀自动机)
传送门
题面:
czh得意的向cry炫耀自己的英文水平,cry很不屑的扔给czh一篇文章,虽然czh看完之后头晕眼花,但他还是决定猜测一下文章中单词的意思,由于文章连空格都没有,这大大增加了阅读的难度,于是他决定退而求其次,只需要计算一下从第L个字符到第R个字符中有多少个可能的互不相同单词(任意长度 >=1 ,任意组合的字符都是一个可能的单词)就行了,这时候就需要聪明的你们来帮忙了。
Input
第一行读入T,表示数据组数(T<=5)
对于每组数据,第一行读入字符串S len[S]<2000 为cry给出的文章
第二行一个正整数Q(Q<=10000)表示提问次数
接下来Q行,每行两个正整数L,R
Output
对于每次提问,每行输出一个答案
Sample Input
2
cchch
3
1 4
2 5
3 4
hzzhz
4
5 5
3 5
1 4
3 3
Sample Output
8
7
3
1
5
8
1
题意:
给你一个字符串,给你q个询问,问你在字符串区间[l,r]中有多少个本质不同的字串。
题目分析:
后缀自动机模板题呀。
个人理解后缀自动机就是将一颗后缀树的空间不断压缩而形成的一张有向图。而建立出来的后缀自动机中的从root开始走的到结尾的所有不同的路径均代表着一种本质不同的字串。其中有两个很重要的数字,其中l[i]代表了从root走到第i个结点的距离,而fa指针则代表着第i个结点的后缀连接(即fa[i]是i的后缀)
而在我们加入每一个字符的过程中,加入该字符所增加的路径为,因此根据定义,则此时整个串所增加的本质不同的回文串的个数也为。
而对于这个题,因为询问比较大(1e4)但字符串str长度较小(1e2),因此我们可以考虑用后缀自动机预处理出来字符串str的所有位置的字串的个数,之后o1查询即可。
代码:
#include <bits/stdc++.h>
#define maxn 2005
using namespace std;
char str[maxn];
int len;
int ans[maxn][maxn];
struct SAM{
int next[maxn][26],fa[maxn],l[maxn];
int last,id;
int tot;
int newnode(){
for(int i=0;i<26;i++) next[id][i]=0;
fa[id]=l[id]=0;
return id++;
}
void init(){
last=id=1;
newnode();
tot=0;
}
int Insert(int c){
int p=last;
int np=newnode();
l[np]=l[p]+1;
last=np;
while(p&&!next[p][c]) next[p][c]=np,p=fa[p];
if(!p) fa[np]=1;
else{
int q=next[p][c];
if(l[q]==l[p]+1) fa[np]=q;
else{
int nq=newnode();
memcpy(next[nq],next[q],sizeof(next[q]));
l[nq]=l[p]+1;
fa[nq]=fa[q];
fa[np]=fa[q]=nq;
while(next[p][c]==q) next[p][c]=nq,p=fa[p];
}
}
tot+=l[last]-l[fa[last]];
return tot;
}
}sam;
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%s",str);
len=strlen(str);
sam.init();
for(int i=0;i<len;i++){
sam.init();
for(int j=i;j<len;j++){
ans[i+1][j+1]=sam.Insert(str[j]-'a');
}
}
int m;
scanf("%d",&m);
while(m--){
int l,r;
scanf("%d%d",&l,&r);
cout<<ans[l][r]<<endl;
}
}
return 0;
}