BZOJ 2946 POI2000 公共串 后缀自动机(多串最长公共子串)

Posted KKKorange的代码盒子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ 2946 POI2000 公共串 后缀自动机(多串最长公共子串)相关的知识,希望对你有一定的参考价值。

题意概述:给出N个字符串,每个串的长度<=2000(雾。。。可能是当年的年代太久远机子太差了),问这N个字符串的最长公共子串长度为多少。(N<=5)

 

抛开数据结构,先想想朴素做法。

设计一种稳定的暴力算法。可以想到这样一种做法:首先确定一个串,枚举每个位置,然后暴力计算其他每个串以这个位置开头的最长匹配,取最小值,就是在公共子串在我们确定下来的串的这个位置开头的时候所能得到的最长公共子串。不难发现把这个问题转化成后缀的形式也是一样的。同时发现可能在枚举多个位置的时候答案甚至最后构造出来的串都是一模一样的。

所以就有了SAM计算最长公共子串的做法(实际上后缀数组也可以做就是可怜的多了个log)。

首先建立第一个串的SAM,然后用剩下的串在上面跑,计算每个串在所有状态上的最长匹配。从初始状态一路走下来的路径就是一个公共子串,而现在经历的边数就是你在这个状态下的最长匹配长度;失配的时候就沿着pa一直调整,根据SAM的原理,初始状态到pa[i]的所有路径一定是初始状态到i的所有路径形成的字符串的后缀,并且是尽量长的。当在i的祖先状态p找到一条可以匹配的边的时候就重新出发,把当前的最长匹配的长度设成MAX(p)(len-MAX(p)那一段导致匹配失败了,丢掉)。

根据上面暴力的做法,我们应该要统计在每个位置作为公共子串结束位置的时候的最长匹配,于是这个时候就要在parent树中自下而上用每个状态上的最长匹配更新了。虽然一个状态的right里面有一堆r,但是在理解到SAM只是对所有后缀的压缩储存之后我们只要最大的那个就可以了。利用topo序可以做到线性时间内完成。

 

整个算法的时间复杂度是O(N*L),N变成100000都没有什么大毛病?(对gets的深深恐惧,我以后再也不随便用gets了QWQ)

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<algorithm>
 6 #include<cmath>
 7 #include<queue>
 8 #include<set>
 9 #include<map>
10 #include<vector>
11 #include<cctype>
12 using namespace std;
13 const int maxn=2005;
14 
15 int N; char S[maxn];
16 struct Suffix_Automaton{
17     static const int maxn=4005;
18     static const int sigma_sz=26;
19     int sz,last,to[maxn][sigma_sz],mx[maxn],pa[maxn],match[maxn],f[maxn];
20     int topo[maxn],c[maxn];
21     Suffix_Automaton(){
22         sz=last=1; memset(to[1],0,sizeof(to[1]));
23     }
24     int newnode(){
25         memset(to[++sz],0,sizeof(to[sz]));
26         mx[sz]=pa[sz]=0;
27         return sz;
28     }
29     void extend(int w){
30         int p=last,np=newnode(); last=np;
31         mx[np]=mx[p]+1;
32         while(p&&!to[p][w]) to[p][w]=np,p=pa[p];
33         if(!p) pa[np]=1;
34         else{
35             int q=to[p][w];
36             if(mx[q]==mx[p]+1) pa[np]=q;
37             else{
38                 int nq=newnode(); mx[nq]=mx[p]+1;
39                 memcpy(to[nq],to[q],sizeof(to[q]));
40                 pa[nq]=pa[q];
41                 pa[q]=pa[np]=nq;
42                 while(p&&to[p][w]==q) to[p][w]=nq,p=pa[p];
43             }
44         }
45     }
46     void ready(int n){
47         memset(c,0,sizeof(c));
48         for(int i=1;i<=sz;i++) c[mx[i]]++;
49         for(int i=1;i<=n;i++) c[i]+=c[i-1];
50         for(int i=sz;i>=1;i--) topo[c[mx[i]]--]=i;
51     }
52     void run(char *s){
53         memset(f,0,sizeof(f));
54         int i=0,p=1,len=0,n=strlen(s);
55         while(i<n){
56             int j=s[i]-a;
57             while(p&&!to[p][j]) p=pa[p],len=mx[p];
58             if(!p) p=1,len=0;
59             else p=to[p][j],f[p]=max(f[p],++len);
60             i++;
61         }
62         for(int i=sz;i>=1;i--){
63             int x=topo[i];
64             f[pa[x]]=max(f[pa[x]],f[x]);
65         }
66         for(int i=1;i<=sz;i++)
67             match[i]=min(match[i],f[i]);
68     }
69 }sam;
70 
71 void work()
72 {
73     scanf("%d%s",&N,S);
74     int n=strlen(S);
75     for(int i=0;i<n;i++) sam.extend(S[i]-a);
76     sam.ready(n);
77     for(int i=1;i<=sam.sz;i++) sam.match[i]=sam.mx[i];
78     for(int i=1;i<N;i++) { scanf("%s",S); sam.run(S); }
79     int ans=0;
80     for(int i=1;i<=sam.sz;i++)
81         ans=max(ans,sam.match[i]);
82     printf("%d\n",ans);
83 }
84 int main()
85 {
86     work();
87     return 0;
88 }

 

以上是关于BZOJ 2946 POI2000 公共串 后缀自动机(多串最长公共子串)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案

BZOJ 2946 POI2000 公共串 后缀自动机(多串最长公共子串)

BZOJ 2946: [Poi2000]公共串

bzoj2946 [Poi2000]公共串(SA,SAM)

bzoj 2946 [Poi2000]公共串

[POI2000]公共串 - 后缀数组