沙茶博主在抢救之前的字符串水平:KMP+Manacher+哈希+(不熟的)AC自动机
所以自我抢救系列又多了一篇
(这人怎么什么都要抢救,回炉重造吧)
表示12月集训后只学会了怎么写SAM,完全不知道怎么用
断断续续到了省选前三周,再不学就不用学了=。=
所以这篇文章同样不适合用来学SAM
(主要是抢救SAM,如果还有空就再看看AC自动机
(废话结束)
(个人意见:SAM比SA好写好调好理解,表示完全不知道SA在干什么)
(如果一定要我求SA?往下看)
(当然只是沙茶博主的个人意见)
SAM那一套理论.jpg
right集合,parent树,trans边,maxlen的基本定义就不说了(当然大家叫法都不一样)
广义SAM:每次插完一个串lst指回根节点重新插
关于right集合
建出parent树之后DFS一遍得到每个节点的子树大小就是每个节点的right集合的大小,从下往上right集合的大小变大(废话
关于parent树
一个点的父亲代表的状态是这个点代表的状态的后缀
关于trans边
按着trans边从下往上统计size,得到的每个节点的大小就是以它开始的子串数目
如果按照right的大小来得到的是本质相同算多个的,直接做得到的是本质不同的
关于maxlen
每个状态$s$代表的长度区间是$([len[fth[s]],len[s])$,从下往上len变小(废话
好的这些东西看看就好,更有用的在下面
哦对了,所谓的“(把某个串)放到SAM上跑”就是说从根节点出发有当前字符的trans边就走,没有就跳parent树直到有了再走一次(或者一直没有回到了根节点)
具体应用
①求本质不同子串数目
所有节点的$\sum len[i]-len[fth[i]]$
例题1 HAOI 2016 找相同字符
标记一下两个串来区分,然后乘法原理统计
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=800005; 6 int p[N],noww[N],goal[N]; 7 int trs[N][26],fth[N],len[N],siz[N][2]; 8 int l1,l2,cnt,tot,lst; long long ans; char s1[N],s2[N]; 9 void Link(int f,int t) 10 { 11 noww[++cnt]=p[f]; 12 goal[cnt]=t,p[f]=cnt; 13 } 14 void Insert(int ch,int tp) 15 { 16 int nde=lst,newn=++tot; lst=newn; 17 siz[newn][tp]=1,len[newn]=len[nde]+1; 18 while(nde&&!trs[nde][ch]) 19 trs[nde][ch]=newn,nde=fth[nde]; 20 if(!nde) fth[newn]=1; 21 else 22 { 23 int tran=trs[nde][ch]; 24 if(len[tran]==len[nde]+1) 25 fth[newn]=tran; 26 else 27 { 28 int rnde=++tot; len[rnde]=len[nde]+1; 29 for(int i=0;i<=25;i++) trs[rnde][i]=trs[tran][i]; 30 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 31 while(nde&&trs[nde][ch]==tran) 32 trs[nde][ch]=rnde,nde=fth[nde]; 33 } 34 } 35 } 36 void DFS(int nde) 37 { 38 for(int i=p[nde],g;i;i=noww[i]) 39 { 40 DFS(g=goal[i]); 41 siz[nde][0]+=siz[g][0]; 42 siz[nde][1]+=siz[g][1]; 43 } 44 ans+=1ll*(len[nde]-len[fth[nde]])*siz[nde][0]*siz[nde][1]; 45 } 46 int main() 47 { 48 register int i; 49 scanf("%s%s",s1+1,s2+1); 50 l1=strlen(s1+1),l2=strlen(s2+1),lst=tot=1; 51 for(i=1;i<=l1;i++) Insert(s1[i]-'a',0); lst=1; 52 for(i=1;i<=l2;i++) Insert(s2[i]-'a',1); 53 for(i=1;i<=tot;i++) Link(fth[i],i); 54 DFS(1),printf("%lld",ans); 55 return 0; 56 }
例题2 USACO17DEC Standing Out from the Herd
同样还是标记每个串,只有子树里都一样才统计
1 #include<map> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=200005; 7 int p[N],noww[N],goal[N]; 8 int bel[N],col[N],fth[N],len[N]; 9 int n,lth,cnt,tot,lst; long long ans[N]; 10 char str[N]; map<int,int> trs[N]; 11 void Link(int f,int t) 12 { 13 noww[++cnt]=p[f]; 14 goal[cnt]=t,p[f]=cnt; 15 } 16 int Insert(int ch) 17 { 18 int nde=lst,newn=++tot; 19 lst=newn,len[newn]=len[nde]+1; 20 while(nde&&!trs[nde].count(ch)) 21 trs[nde][ch]=newn,nde=fth[nde]; 22 if(!nde) fth[newn]=1; 23 else 24 { 25 int tran=trs[nde][ch]; 26 if(len[tran]==len[nde]+1) 27 fth[newn]=tran; 28 else 29 { 30 int rnde=++tot; 31 len[rnde]=len[nde]+1,trs[rnde]=trs[tran]; 32 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 33 while(nde&&trs[nde][ch]==tran) 34 trs[nde][ch]=rnde,nde=fth[nde]; 35 } 36 } 37 return newn; 38 } 39 void DFS(int nde) 40 { 41 for(int i=p[nde],g;i;i=noww[i]) 42 { 43 DFS(g=goal[i]); 44 int &b1=bel[nde],b2=bel[g]; 45 if(!b1) b1=b2; else if(b1!=b2) b1=-1; 46 } 47 } 48 int main() 49 { 50 register int i,j; 51 scanf("%d",&n),tot=1; 52 for(i=1;i<=n;i++) 53 { 54 scanf("%s",str+1),lst=1,lth=strlen(str+1); 55 for(j=1;j<=lth;j++) bel[Insert(str[j]-'a')]=i; 56 } 57 for(i=1;i<=tot;i++) Link(fth[i],i); DFS(1); 58 for(i=1;i<=tot;i++) if(~bel[i]) ans[bel[i]]+=len[i]-len[fth[i]]; 59 for(i=1;i<=n;i++) printf("%lld\n",ans[i]); 60 return 0; 61 }
广义SAM也一样做,这题从每个叶子开始跑一次DFS插进去就好了
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=4000005; 6 int trs[N][10],fth[N],len[N],siz[N]; 7 int p[N],noww[N],goal[N],deg[N],col[N]; 8 int n,k,t1,t2,cnt,tot; long long ans; 9 void Link(int f,int t) 10 { 11 noww[++cnt]=p[f],p[f]=cnt; 12 goal[cnt]=t,deg[t]++; 13 noww[++cnt]=p[t],p[t]=cnt; 14 goal[cnt]=f,deg[f]++; 15 } 16 int Insert(int ch,int lst) 17 { 18 int nde=lst,newn=++tot; 19 lst=newn,len[newn]=len[nde]+1; 20 while(nde&&!trs[nde][ch]) 21 trs[nde][ch]=newn,nde=fth[nde]; 22 if(!nde) fth[newn]=1; 23 else 24 { 25 int tran=trs[nde][ch]; 26 if(len[tran]==len[nde]+1) 27 fth[newn]=tran; 28 else 29 { 30 int rnde=++tot; len[rnde]=len[nde]+1; 31 for(int i=0;i<=9;i++) trs[rnde][i]=trs[tran][i]; 32 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 33 while(nde&&trs[nde][ch]==tran) 34 trs[nde][ch]=rnde,nde=fth[nde]; 35 } 36 } 37 return newn; 38 } 39 void DFS(int nde,int fth,int lst) 40 { 41 lst=Insert(col[nde],lst); 42 for(int i=p[nde];i;i=noww[i]) 43 if(goal[i]!=fth) DFS(goal[i],nde,lst); 44 } 45 int main() 46 { 47 register int i; 48 scanf("%d%d",&n,&k),tot=1; 49 for(i=1;i<=n;i++) scanf("%d",&col[i]); 50 for(i=1;i<n;i++) scanf("%d%d",&t1,&t2),Link(t1,t2); 51 for(i=1;i<=n;i++) if(deg[i]==1) DFS(i,0,1); 52 for(i=1;i<=tot;i++) ans+=len[i]-len[fth[i]]; 53 printf("%lld",ans); 54 return 0; 55 }
ex① 多次区间询问
利用zrq讲的那个套路思路
②两个串的最长公共子串
一个建SAM一个放上去跑,走一次trans长度加1,跳parent将长度同步为当前节点的len
ex② 多个串
并没有什么变化,每个串都跑一遍,在每个节点都将答案取min
例题 POI 2000 公共串
1 #include<map> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=20005; 7 int n,lth,tot,lst,anss; char str[N]; 8 int trs[N][26],fth[N],len[N],mxx[N],ans[N]; 9 void Maxi(int &x,int y){if(x<y) x=y;} 10 void Mini(int &x,int y){if(x>y) x=y;} 11 void Insert(int ch) 12 { 13 int nde=lst,newn=++tot; 14 lst=newn,len[newn]=len[nde]+1; 15 while(nde&&!trs[nde][ch]) 16 trs[nde][ch]=newn,nde=fth[nde]; 17 if(!nde) fth[newn]=1; 18 else 19 { 20 int tran=trs[nde][ch]; 21 if(len[tran]==len[nde]+1) 22 fth[newn]=tran; 23 else 24 { 25 int rnde=++tot; len[rnde]=len[nde]+1; 26 for(int i=0;i<=25;i++) trs[rnde][i]=trs[tran][i]; 27 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 28 while(nde&&trs[nde][ch]==tran) 29 trs[nde][ch]=rnde,nde=fth[nde]; 30 } 31 } 32 } 33 int main() 34 { 35 scanf("%d%s",&n,str+1); 36 tot=lst=1,lth=strlen(str+1); 37 for(int i=1;i<=lth;i++) Insert(str[i]-'a'); 38 for(int i=1;i<=tot;i++) ans[i]=len[i]; 39 if(n==1) printf("%d",lth); 40 else 41 { 42 for(int i=2;i<=n;i++) 43 { 44 scanf("%s",str+1); 45 lth=strlen(str+1); 46 int nde=1,tmp=0; 47 memset(mxx,0,sizeof mxx); 48 for(int j=1;j<=lth;j++) 49 { 50 int ch=str[j]-'a'; 51 while(nde&&!trs[nde][ch]) 52 nde=fth[nde],tmp=len[nde]; 53 if(nde) tmp++,nde=trs[nde][ch]; 54 else tmp=0,nde=1; 55 Maxi(mxx[nde],tmp); 56 } 57 for(int i=1;i<=tot;i++) Mini(ans[i],mxx[i]); 58 } 59 for(int i=1;i<=tot;i++) Maxi(anss,ans[i]); 60 printf("%d",anss); 61 } 62 return 0; 63 }
③第k小子串
上面已经说了怎么统计以每个节点开始的本质相同算多个/一个的子串数目,求第k小就从根节点沿着trans往下走,每次扣掉对应的大小
(我还想怎么求第$k$大子串,后来发现第$k$小子串都能求了不会求第$k$大是不是有点沙茶啊=。=)
④最小表示法
把串插两遍,然后从根节点开始沿着字典序最小的trans走,记录走过的字符
例题 洛谷 1368 工艺
然而你可以看到SAM跑的非常惨,因为依赖map多一个log,而且空间也是四倍的
1 #include<map> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=1200005; 7 map<int,int> trs[N]; 8 int num[N],fth[N],len[N]; 9 int n,tot,lst; long long ans; 10 void Insert(int ch) 11 { 12 int nde=lst,newn=++tot; 13 lst=newn,len[newn]=len[nde]+1; 14 while(nde&&!trs[nde].count(ch)) 15 trs[nde][ch]=newn,nde=fth[nde]; 16 if(!nde) fth[newn]=1; 17 else 18 { 19 int tran=trs[nde][ch]; 20 if(len[tran]==len[nde]+1) 21 fth[newn]=tran; 22 else 23 { 24 int rnde=++tot; 25 len[rnde]=len[nde]+1,trs[rnde]=trs[tran]; 26 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 27 while(nde&&trs[nde][ch]==tran) 28 trs[nde][ch]=rnde,nde=fth[nde]; 29 } 30 } 31 } 32 int main() 33 { 34 scanf("%d",&n),tot=lst=1; 35 for(int i=1;i<=n;i++) scanf("%d",&num[i]); 36 for(int i=1;i<=n;i++) Insert(num[i]); 37 for(int i=1;i<=n;i++) Insert(num[i]); 38 for(int i=1,nde=1;i<=n;i++) 39 { 40 printf("%d ",trs[nde].begin()->first); 41 nde=trs[nde].begin()->second; 42 } 43 return 0; 44 }
⑤子串的出现次数
记录右端点对应的parent树节点,在倍增跳parent树跳到最靠上的len小于等于该串长度的节点,该节点的siz就是子串出现的次数
例题 APIO 2014 回文串
本质不同的回文串只有$O(n)$级别,所以manacher+SAM就好了
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=600060,K=20; 6 7 int p[N],noww[N],goal[N]; 8 int rnk[N],bkt[N],mul[N][K]; 9 int trs[N][26],fth[N],len[N],siz[N],num[N]; 10 char str[N>>1],Str[N]; 11 int lth,lst,cnt,tot,mid,maxr; 12 long long ans; 13 14 void link(int f,int t) 15 { 16 noww[++cnt]=p[f]; 17 goal[cnt]=t,p[f]=cnt; 18 } 19 20 void DFS(int nde,int fth) 21 { 22 mul[nde][0]=fth; 23 for(int i=p[nde];i;i=noww[i]) 24 DFS(goal[i],nde),siz[nde]+=siz[goal[i]]; 25 for(int i=1;mul[nde][i];i++) 26 mul[nde][i]=mul[mul[nde][i-1]][i-1]; 27 } 28 void Insert(int ch,int ps) 29 { 30 int nde=lst,newn=++tot; 31 num[ps]=lst=newn,siz[newn]=1,len[newn]=len[nde]+1; 32 while(nde&&!trs[nde][ch]) 33 trs[nde][ch]=newn,nde=fth[nde]; 34 if(!nde) fth[newn]=1; 35 else 36 { 37 int tran=trs[nde][ch]; 38 if(len[tran]==len[nde]+1) 39 fth[newn]=tran; 40 else 41 { 42 int rnde=++tot; len[rnde]=len[nde]+1; 43 for(int i=0;i<=25;i++) trs[rnde][i]=trs[tran][i]; 44 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 45 while(nde&&trs[nde][ch]==tran) 46 trs[nde][ch]=rnde,nde=fth[nde]; 47 } 48 } 49 } 50 void prework() 51 { 52 register int i,j; 53 scanf("%s",str+1),lth=strlen(str+1),lst=tot=1; 54 for(i=1;i<=lth;i++) Insert(str[i]-'a',i); 55 for(i=1;i<=tot;i++) link(fth[i],i); DFS(1,0); 56 } 57 void Solve(int l,int r) 58 { 59 l=(l+l%2)/2,r=(r-r%2)/2; 60 if(l>r) return ; 61 int nde=num[r],lth=r-l+1; 62 for(int i=19;~i;i--) 63 if(lth<=len[mul[nde][i]]) 64 nde=mul[nde][i]; 65 ans=max(ans,1ll*lth*siz[nde]); 66 } 67 void Manacher() 68 { 69 register int i; 70 int newl=2*lth+1; 71 for(i=1;i<=newl;i++) 72 Str[i]=(i&1)?'?':str[i>>1]; 73 Str[0]='>',Str[newl+1]='<'; 74 for(i=1;i<=newl;i++) 75 { 76 fth[i]=(maxr>=i)?min(maxr-i+1,fth[2*mid-i]):1; 77 Solve(i-fth[i]+1,i+fth[i]-1); 78 while(Str[i-fth[i]]==Str[i+fth[i]]) 79 fth[i]++,Solve(i-fth[i]+1,i+fth[i]-1); 80 if(i+fth[i]-1>maxr) maxr=i+fth[i]-1,mid=i; 81 } 82 } 83 int main() 84 { 85 prework(),Manacher(); 86 printf("%lld",ans); 87 return 0; 88 }
⑥两个P(refix)的LCS(uffix)
两个P的LCS就是他们parent树上对应节点的LCA,同理的,想要找S的LCP把串反过来插进去即可
一个事实是所有位置对所代表的的P的LCS之和等于所有位置对所代表的S的LCP之和
例题 AHOI 2013 差异
通过上面那个东西转变成parent树上两两路径之和,统计每条边的贡献即可
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=1000005; 6 int p[N],noww[N],goal[N]; 7 int trs[N][26],fth[N],len[N],siz[N]; 8 int lth,cnt,tot,lst; long long ans; char str[N]; 9 void Link(int f,int t) 10 { 11 noww[++cnt]=p[f]; 12 goal[cnt]=t,p[f]=cnt; 13 } 14 void Insert(int ch) 15 { 16 int nde=lst,newn=++tot; lst=newn; 17 siz[newn]=1,len[newn]=len[nde]+1; 18 while(nde&&!trs[nde][ch]) 19 trs[nde][ch]=newn,nde=fth[nde]; 20 if(!nde) fth[newn]=1; 21 else 22 { 23 int tran=trs[nde][ch]; 24 if(len[tran]==len[nde]+1) 25 fth[newn]=tran; 26 else 27 { 28 int rnde=++tot; len[rnde]=len[nde]+1; 29 for(int i=0;i<=25;i++) trs[rnde][i]=trs[tran][i]; 30 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 31 while(nde&&trs[nde][ch]==tran) 32 trs[nde][ch]=rnde,nde=fth[nde]; 33 } 34 } 35 } 36 void DFS(int nde) 37 { 38 for(int i=p[nde],g;i;i=noww[i]) 39 DFS(g=goal[i]),siz[nde]+=siz[g]; 40 for(int i=p[nde],g;i;i=noww[i]) 41 g=goal[i],ans+=1ll*(len[g]-len[nde])*(lth-siz[g])*siz[g]; 42 } 43 int main() 44 { 45 register int i; 46 scanf("%s",str+1); 47 lth=strlen(str+1),lst=tot=1; 48 for(i=lth;i;i--) Insert(str[i]-'a'); 49 for(i=1;i<=tot;i++) Link(fth[i],i); 50 DFS(1),printf("%lld",ans); 51 return 0; 52 }
类似的 NOI 2015 品酒大会
树上DFS一遍求最小值和最大值(记录最小值是因为可能两个负数乘起来更大),思路是不是非常清新啊(踩踩后缀数组
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=600005; 6 const long long inf=1e18; 7 int lth,cnt,tot,lst; char str[N]; 8 int p[N],noww[N],goal[N],trs[N][26],fth[N],len[N],siz[N]; 9 long long del[N],mini[N],maxi[N],mem1[N],mem2[N],ans1[N],ans2[N]; 10 void Maxi(long long &x,long long y){if(x<y) x=y;} 11 void Mini(long long &x,long long y){if(x>y) x=y;} 12 void Link(int f,int t) 13 { 14 noww[++cnt]=p[f]; 15 goal[cnt]=t,p[f]=cnt; 16 } 17 int Insert(int ch) 18 { 19 int nde=lst,newn=++tot; lst=newn; 20 siz[newn]=1,len[newn]=len[nde]+1; 21 while(nde&&!trs[nde][ch]) 22 trs[nde][ch]=newn,nde=fth[nde]; 23 if(!nde) fth[newn]=1; 24 else 25 { 26 int tran=trs[nde][ch]; 27 if(len[tran]==len[nde]+1) 28 fth[newn]=tran; 29 else 30 { 31 int rnde=++tot; len[rnde]=len[nde]+1; 32 for(int i=0;i<=25;i++) trs[rnde][i]=trs[tran][i]; 33 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 34 while(nde&&trs[nde][ch]==tran) 35 trs[nde][ch]=rnde,nde=fth[nde]; 36 } 37 } 38 return newn; 39 } 40 void DFS(int nde) 41 { 42 for(int i=p[nde],g;i;i=noww[i]) 43 { 44 DFS(g=goal[i]); 45 mem1[nde]+=1ll*siz[nde]*siz[g],siz[nde]+=siz[g]; 46 if(mini[nde]!=inf) Maxi(mem2[nde],mini[nde]*mini[g]); 47 if(maxi[nde]!=-inf) Maxi(mem2[nde],maxi[nde]*maxi[g]); 48 Maxi(maxi[nde],maxi[g]),Mini(mini[nde],mini[g]); 49 } 50 } 51 void Init() 52 { 53 lst=tot=1; 54 for(int i=1;i<=2*lth;i++) 55 mini[i]=inf,maxi[i]=mem2[i]=ans2[i]=-inf; 56 } 57 int main() 58 { 59 register int i; 60 scanf("%d%s",<h,str+1),Init(); 61 for(i=1;i<=lth;i++) scanf("%lld",&del[i]); 62 for(i=lth;i;i--) 63 { 64 int nde=Insert(str[i]-'a'); 65 maxi[nde]=mini[nde]=del[i]; 66 } 67 for(i=1;i<=tot;i++) Link(fth[i],i); DFS(1); 68 for(i=1;i<=tot;i++) 69 { 70 ans1[len[i]]+=mem1[i]; 71 Maxi(ans2[len[i]],mem2[i]); 72 } 73 for(i=lth-2;~i;i--) ans1[i]+=ans1[i+1],Maxi(ans2[i],ans2[i+1]); 74 for(i=0;i<lth;i++) printf("%lld %lld\n",ans1[i],ans2[i]==-inf?0:ans2[i]); 75 return 0; 76 }
ex⑥ 多次询问一些P/S两两的LCS/LCP之和
在parent树上建虚树之后DP
⑦求一个字符串中只出现了一次的所有子串
这些子串对应的状态都是parent树里的叶子,对每个状态记录它对应的任意一个子串的结束位置endp,每个只出现一次的串就对应$[endp-len+1,endp]$这个区间
⑧两个字符串a,b,多次询问a的一个区间$[l,r]$在b上出现了多少次
对b建SAM,把a放上去跑,记录每个右端点最长匹配长度和对应的节点。对于每个询问如果右端点对应的匹配长度不够就没出现,够就变成⑤从对应的节点开始跳
⑨给出一坨字符串$a[]$和一个字符串b,每次询问b的一个子串在$a[]$的一段字符串$a[l]......a[r]$中哪个出现次数最大(CF666E)
对$a[]$建广义SAM,每个节点用线段树记录$a[]$中串的出现情况,跑一次线段树合并。然后用⑤的方法定位状态,在对应的线段树上查询
⑩给出一坨字符串,询问每个串有多少子串在至少$k$个串里出现过
建立广义SAM,每个节点用set记录这个状态被哪些串包含。把每个串放在SAM上跑,当匹配的点出现次数小于k时跳parent树直到次数不小于k,将对应的len计入答案
套路有一些,还有就是反复倒腾SAM的性质和一些基本的操作
最后还有一个问题,如果一定要我求SA怎么办呢?
SAM也能求!按说是$O(n)$的,然而因为一般空间开不下所以只能用map=。=
对反串建SAM,记录每个节点是不是后缀,按trans跑拓扑排序后建树(不是parent树)DFS一遍,具体看代码
UOJ 模板 后缀排序(因为常数太大惨遭后缀排序吊打)
1 #include<map> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=2000005,M=70; 7 int p[N],noww[N],goal[N],fth[N],len[N]; 8 int suf[N],pos[N],rnk[N],bkt[M],par[N]; 9 int SA[N],hgt[N],lth,cnt,tot,lst,dfn; 10 map<int,int> trs[N]; char str[N]; 11 int T(char ch) 12 { 13 if(ch>='0'&&ch<='9') return ch-'0'; 14 else if(ch>='A'&&ch<='Z') return ch-'A'+11; 15 else return ch-'a'+37; 16 } 17 void Link(int f,int t) 18 { 19 noww[++cnt]=p[f]; 20 goal[cnt]=t,p[f]=cnt; 21 } 22 void Insert(int ch,int ps) 23 { 24 int nde=lst,newn=++tot; lst=newn; 25 len[newn]=pos[newn]=len[nde]+1,suf[newn]=ps; 26 while(nde&&!trs[nde].count(ch)) 27 trs[nde][ch]=newn,nde=fth[nde]; 28 if(!nde) fth[newn]=1; 29 else 30 { 31 int tran=trs[nde][ch]; 32 if(len[tran]==len[nde]+1) 33 fth[newn]=tran; 34 else 35 { 36 int rnde=++tot; pos[rnde]=pos[tran]; 37 len[rnde]=len[nde]+1,trs[rnde]=trs[tran]; 38 fth[rnde]=fth[tran],fth[tran]=fth[newn]=rnde; 39 while(nde&&trs[nde][ch]==tran) 40 trs[nde][ch]=rnde,nde=fth[nde]; 41 } 42 } 43 } 44 void Pre() 45 { 46 for(int i=1;i<=tot;i++) rnk[i]=T(str[lth-pos[i]+1+len[fth[i]]]),bkt[rnk[i]]++; 47 for(int i=1;i<=62;i++) bkt[i]+=bkt[i-1]; 48 for(int i=1;i<=tot;i++) par[bkt[rnk[i]]--]=i; 49 for(int i=tot;i;i--) Link(fth[par[i]],par[i]); 50 } 51 void DFS(int nde) 52 { 53 if(suf[nde]) SA[++dfn]=suf[nde]; 54 for(int i=p[nde];i;i=noww[i]) DFS(goal[i]); 55 } 56 void Height() 57 { 58 for(int i=1;i<=lth;i++) rnk[SA[i]]=i; 59 for(int i=1;i<=lth;i++) 60 { 61 int h=max(hgt[rnk[i-1]]-1,0),g=SA[rnk[i]-1]; 62 while(str[i+h]==str[g+h]) h++; hgt[rnk[i]]=h; 63 } 64 } 65 int main() 66 { 67 scanf("%s",str+1),lth=strlen(str+1),tot=lst=1; 68 for(int i=lth;i;i--) Insert(T(str[i]),i); 69 Pre(),DFS(1); 70 for(int i=1;i<=lth;i++) printf("%d ",SA[i]); 71 Height(); puts(""); 72 for(int i=2;i<=lth;i++) printf("%d ",hgt[i]); 73 return 0; 74 }