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

Description

给出几个由小写字母构成的单词,求它们最长的公共子串的长度。

任务:

l        读入单词

l        计算最长公共子串的长度

l        输出结果

Input

文件的第一行是整数 n,1<=n<=5,表示单词的数量。接下来n行每行一个单词,只由小写字母组成,单词的长度至少为1,最大为2000。

Output

仅一行,一个整数,最长公共子串的长度。

Sample Input

3
abcb
bca
acbc

Sample Output

【思路】

  多串求LCS。

  主要是想找一下SAM的优越感 :) velui good

  后缀数组划分height需要注意不少细节 <_<,然后不停debug

 

【代码】

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 using namespace std;
 5
 6 const int N = 2000*5+10;
 7
 8 char s[N],tmp[N];
 9 int sa[N],c[N],t[N],t2[N],rank[N],height[N];
10
11 void build_sa(int m,int n) {
12     int i,k,*x=t,*y=t2;
13     for(i=0;i<m;i++) c[i]=0;
14     for(i=0;i<n;i++) c[x[i]=s[i]]++;
15     for(i=1;i<m;i++) c[i]+=c[i-1];
16     for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
17     for(k=1;k<=n;k<<=1) {
18         int p=0;
19         for(i=n-k;i<n;i++) y[p++]=i;
20         for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
21         for(i=0;i<m;i++) c[i]=0;
22         for(i=0;i<n;i++) c[x[y[i]]]++;
23         for(i=1;i<m;i++) c[i]+=c[i-1];
24         for(i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
25         swap(x,y);
26         p=1; x[sa[0]]=0;
27         for(i=1;i<n;i++)
28             x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]? p-1:p++;
29         if(p>=n) break;
30         m=p;
31     }
32 }
33 void get_height(int n) {
34     int i,j,k=0;
35     for(int i=0;i<=n;i++) rank[sa[i]]=i;
36     for(int i=0;i<n;i++) {
37         if(k) k--;
38         j=sa[rank[i]-1];
39         while(s[j+k]==s[i+k]) k++;
40         height[rank[i]]=k;
41     }
42 }
43
44 int flag[6],cr[N];
45 bool can(int M,int n,int k) {
46     int kase=0,cnt=1;
47     flag[cr[sa[0]]]=kase;
48     for(int i=1;i<n;i++)
49     if(height[i]<M)
50         cnt=1,flag[cr[sa[i]]]=++kase;
51     else {
52         int r=cr[sa[i]];
53         if(flag[r]!=kase) cnt++,flag[r]=kase;
54         if(cnt==k) return true;
55     }
56     return false;
57 }
58
59 int main() {
60     int n; scanf("%d",&n);
61     int block=n,sz=0;
62     for(int i=0;i<n;i++) {
63         scanf("%s",tmp);
64         for(int j=0;tmp[j];j++)
65             s[sz]=tmp[j],cr[sz++]=i;
66         s[sz]=--block,cr[sz++]=i;
67     }
68
69     build_sa(‘z‘+1,sz);
70     get_height(sz);
71
72     int L=0,R=sz;
73     while(L<R) {
74         int M=L+(R-L+1)/2;
75         if(can(M,sz,n)) L=M;
76         else R=M-1;
77     }
78     printf("%d",L);
79     return 0;
80 }

SA

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 using namespace std;
 5
 6 const int N = 2000*5+10;
 7
 8 char s[N];
 9 int last,sz,fa[N],ch[N][26],l[N],mn[N],mx[N];
10 int c[N],b[N];
11
12 void add(int x) {
13     int c=s[x]-‘a‘;
14     int p=last,np=++sz; last=np;
15     mn[np]=l[np]=x+1;
16     for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
17     if(!p) fa[np]=1;
18     else {
19         int q=ch[p][c];
20         if(l[p]+1==l[q]) fa[np]=q;
21         else {
22             int nq=++sz; mn[nq]=l[nq]=l[p]+1;
23             memcpy(ch[nq],ch[q],sizeof(ch[q]));
24             fa[nq]=fa[q];
25             fa[np]=fa[q]=nq;
26             for(;q==ch[p][c];p=fa[p]) ch[p][c]=nq;
27         }
28     }
29 }
30
31 int main() {
32     int n; scanf("%d",&n);
33     last=++sz;
34     scanf("%s",s);
35     for(int i=0;s[i];i++) add(i);
36     for(int i=1;i<=sz;i++) c[l[i]]++;
37     for(int i=1;i<=strlen(s);i++) c[i]+=c[i-1];
38     for(int i=1;i<=sz;i++) b[c[l[i]]--]=i;
39     int ans=0;
40     for(int i=1;i<n;i++) {
41         scanf("%s",s);
42         int p=1,len=0;
43         for(int i=0;s[i];i++) {
44             int c=s[i]-‘a‘;
45             if(ch[p][c]) { len++; p=ch[p][c]; }
46             else {
47                 while(p&&!ch[p][c]) p=fa[p];
48                 if(!p) { len=0; p=1; }
49                 else { len=l[p]+1; p=ch[p][c]; }
50             }
51             mx[p]=max(mx[p],len);
52          }
53          for(int i=sz;i;i--) {
54             int p=b[i];
55             mn[p]=min(mn[p],mx[p]);
56             if(fa[p]) mx[fa[p]]=max(mx[fa[p]],mx[p]);
57             mx[p]=0;
58         }
59     }
60     for(int i=1;i<=sz;i++)
61         if(ans<mn[i]) ans=mn[i];
62     printf("%d",ans);
63     return 0;
64 }

SAM

时间: 2024-11-10 00:37:16

bzoj2946 [Poi2000]公共串(SA,SAM)的相关文章

[BZOJ2946][Poi2000]公共串 后缀自动机

2946: [Poi2000]公共串 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 1367  Solved: 612[Submit][Status][Discuss] Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l        读入单词 l        计算最长公共子串的长度 l        输出结果 Input 文件的第一行是整数 n,1<=n<=5,表示单词的数量.接下来n行每行一个单词

[BZOJ2946] [Poi2000]公共串解题报告|后缀数组

给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 单词个数<=5,每个单词长度<=2000 尽管最近在学的是SAM...但是看到这个题还是忍不住想写SA... (其实是不知道应该怎么用SAM做... 对于后缀数组而言,多个字符串的公共子串与两个处理起来并没有什么区别 只要在中间加一些没有用的字符,将多个字符串拼成一个字符串 然后二分答案,对于一个长度L,在一组除了开头其他height都>=L的区间中如果每个字符串的位置都出现过就可以 应该是第二次这么解决一道公共串的题了.. 然

[BZOJ2946][Poi2000]公共串解题报告|后缀自动机

鉴于SAM要简洁一些...于是又写了一遍这题... 不过很好呢又学到了一些新的东西... 这里是用SA做这道题的方法 首先还是和两个字符串的一样,为第一个字符串建SAM 然后每一个字符串再在这个SAM上跑匹配 然而我们最后要的答案是什么呢? 是某个在所有字符串中匹配长度最小值最大的状态子串 然后对于每一个字符串 我们可以记录它在每一个状态子串上的最大匹配长度 最后需要一个非常关键的转移 就是用当前节点的值更新fail指针指向的节点 比如这种情况 如果一次匹配到左边的三个节点,一次匹配到右边的两个

【二分答案】【分块答案】【字符串哈希】【set】bzoj2946 [Poi2000]公共串

我们二分/分块枚举答案x,暴力把除了最短的字符串以外的其他字符串的x长度子串哈希搞出来,分别扔到set里. 然后暴力枚举最短的字符串的x长度字串,查看是否在全部的set里出现过. #include<cstdio> #include<set> #include<cstring> #include<cmath> using namespace std; typedef unsigned long long ull; set<ull>T[6]; con

【二分答案】【哈希表】【字符串哈希】bzoj2946 [Poi2000]公共串

二分答案,然后搞出hash值扔到哈希表里.期望复杂度O(n*log(n)). #include<cstdio> #include<cstring> #include<vector> using namespace std; typedef unsigned long long ull; const ull seed=29; #define MOD 2007 typedef vector<ull>::iterator VER; vector<ull>

BZOJ2946: [Poi2000]公共串

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2946 先用第一个字符串建后缀自动机,每个节点记录一下每个字符串与之匹配的最大值,那么每个节点对答案的贡献就是所记录的最大值的最小值,把所有刚刚说的最小值取max就是答案了.绕晕了.语文不好真是日了狗. 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #define inf 1<&l

【POI2000】【BZOJ2946】公共串

2946: [Poi2000]公共串 Time Limit: 3 Sec Memory Limit: 128 MB Submit: 286 Solved: 121 [Submit][Status][Discuss] Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l 读入单词 l 计算最长公共子串的长度 l 输出结果 Input 文件的第一行是整数 n,1<=n<=5,表示单词的数量.接下来n行每行一个单词,只由小写字母组成,单词的长度至少为1,最大

【BZOJ2946】公共串 [SAM]

公共串 Time Limit: 3 Sec  Memory Limit: 128 MB[Submit][Status][Discuss] Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l 读入单词 l 计算最长公共子串的长度 l 输出结果 Input 文件的第一行是整数 n ,表示单词的数量.接下来n行每行一个单词,只由小写字母组成,单词的长度至少为1,最大为2000. Output 仅一行,一个整数,最长公共子串的长度. Sample Input

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

题意概述:给出N个字符串,每个串的长度<=2000(雾...可能是当年的年代太久远机子太差了),问这N个字符串的最长公共子串长度为多少.(N<=5) 抛开数据结构,先想想朴素做法. 设计一种稳定的暴力算法.可以想到这样一种做法:首先确定一个串,枚举每个位置,然后暴力计算其他每个串以这个位置开头的最长匹配,取最小值,就是在公共子串在我们确定下来的串的这个位置开头的时候所能得到的最长公共子串.不难发现把这个问题转化成后缀的形式也是一样的.同时发现可能在枚举多个位置的时候答案甚至最后构造出来的串都是