省选前的字符串抢救计划

沙茶博主在抢救之前的字符串水平: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 }
View Code

例题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 }
View Code

例题3 ZJOI2015 诸神眷顾的幻想乡

广义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 }
View Code

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 }
View Code

③第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 }
View Code

⑤子串的出现次数

记录右端点对应的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 }
View Code

⑥两个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 }
View Code

类似的 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",&lth,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 }
View Code

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 }
View Code

 

转载于:https://www.cnblogs.com/ydnhaha/p/10535767.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值