识别子串 (string)——后缀自动机+线段树
Posted chmwt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了识别子串 (string)——后缀自动机+线段树相关的知识,希望对你有一定的参考价值。
题目
【题目描述】
一般地,对于一个字符串 S,和 S 中第 $ i $ 个字符 x,定义子串 $ T=S(i.j) $ 为一个关于 x 的识别子申,当且仅当:
1.$ i leq x leq j $
2.T 在 S 巾只出现一次
比如,对于 banana 的第 $ 5 $ 个字符,“nana”, “anan”,“anana”, “nan”,“banan” 和“banana”都是关于它的识别子串。
说你写一个程序,计算出对对于一个字符串 S,关于 S 的每一位的最短识别子串的长度。
【输入格式】
一行,一个由小写字母组成的字符串 S, 长度不超过 $ 10^5 $
【输出格式】
L 行,每行一个整数,第 $ i $ 行的数据表示关于 S 的第 $ i $ 个元素的最短识别子串有多长.
【样例输入】
agoodcookcooksgoodfood
【样例输出】
1
2
3
3
2
2
3
3
2
2
3
3
2
1
2
3
3
2
1
2
3
4
【数据范围与提示】
对于 20% 的数据 L<=200
对于 40% 的数据 L<=5000
对于 60% 的数据 L<=20000
对于 100% 的数据 L<=100000
题解
可以发现,对于每一个唯一识别的子串,都可以更新一段答案
可以发现,在后缀自动机上的每一个叶子节点 $ x $,都是一段唯一识别的子串
考虑更新,记 $ l=len(x)-len(fa(x)),r=len(x) $,那么对于 $ [1,l-1] $ 的贡献为 $ r-i+1 $,对于 $ [l,r] $ 的贡献为 $ r-l+1 $
可以把 $r-i+1$ 的 $ i $ 提出,转化为 $query(i)-i$,开两棵线段树分别统计即可
代码
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define _(d) while(d(isdigit(ch=getchar()))) 4 using namespace std; 5 int R(){ 6 int x;bool f=1;char ch;_(!)if(ch==‘-‘)f=0;x=ch^48; 7 _()x=(x<<3)+(x<<1)+(ch^48);return f?x:-x;} 8 const int N=1e6+5; 9 int n,las=1,cnt=1,Rt=1; 10 char s[N];bool f[N]; 11 struct node{int ch[30],fa,len;}tr[N]; 12 void extend(int c){ 13 int p=las,np=las=++cnt,q,nq; 14 tr[np].len=tr[p].len+1; 15 while(!tr[p].ch[c]&&p) 16 tr[p].ch[c]=np,p=tr[p].fa; 17 if(!p)return void(tr[np].fa=Rt); 18 if(tr[p].len+1==tr[q=tr[p].ch[c]].len)return void(tr[np].fa=q); 19 tr[nq=++cnt].len=tr[p].len+1; 20 memcpy(tr[nq].ch,tr[q].ch,sizeof tr[q].ch); 21 tr[nq].fa=tr[q].fa,tr[np].fa=tr[q].fa=nq; 22 while(p&&tr[p].ch[c]==q) 23 tr[p].ch[c]=nq,p=tr[p].fa; 24 return; 25 } 26 class seg{ 27 private: 28 #define Ls rt<<1 29 #define Rs rt<<1|1 30 public: 31 int tr[N]; 32 seg(){memset(tr,0x3f,sizeof tr);} 33 void update(int rt,int l,int r,int ql,int qr,int v){ 34 if(ql>qr)return; 35 if(ql<=l&&qr>=r)return void(tr[rt]=min(tr[rt],v)); 36 int mid=l+r>>1; 37 if(ql<=mid)update(Ls,l,mid,ql,qr,v); 38 if(qr>mid)update(Rs,mid+1,r,ql,qr,v); 39 return; 40 } 41 int query(int rt,int l,int r,int k){ 42 if(l==r)return tr[rt]; 43 int mid=l+r>>1; 44 if(k<=mid)return min(tr[rt],query(Ls,l,mid,k)); 45 else return min(tr[rt],query(Rs,mid+1,r,k)); 46 } 47 }T1,T2; 48 int main(){ 49 scanf("%s",s+1),n=strlen(s+1); 50 for(int i=1;i<=n;i++)extend(s[i]-‘a‘); 51 for(int i=1;i<=cnt;i++)f[tr[i].fa]=1; 52 for(int i=1;i<=cnt;i++) 53 if(!f[i]){ 54 int x=tr[i].len-tr[tr[i].fa].len,y=tr[i].len; 55 T1.update(1,1,n,1,x-1,y+1),T2.update(1,1,n,x,y,tr[tr[i].fa].len+1); 56 } 57 for(int i=1;i<=n;i++) 58 printf("%d ",min(T1.query(1,1,n,i)-i,T2.query(1,1,n,i))); 59 return 0; 60 }
以上是关于识别子串 (string)——后缀自动机+线段树的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 1396&&2865 识别子串[后缀自动机 线段树]