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 }

原文地址:https://www.cnblogs.com/KKKorange/p/8536315.html

时间: 2024-10-10 23:52:50

BZOJ 2946 POI2000 公共串 后缀自动机(多串最长公共子串)的相关文章

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp

[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行每行一个单词

URAL 1517 Freedom of Choice(后缀数组,最长公共字串)

题目 输出最长公共字串 #define maxn 200010 int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int cmp(int *r,int a,int b,int l) {return r[a]==r[b]&&r[a+l]==r[b+l];}//yuan lai zhi qian ba zhe li de l cuo dang cheng 1 le ... void da(int *r,int *sa,int n,int m) { int i,j

poj 1226 hdu 1238 Substrings 求若干字符串正串及反串的最长公共子串 2002亚洲赛天津预选题

题目:http://poj.org/problem?id=1226 http://acm.hdu.edu.cn/showproblem.php?pid=1238 其实用hash+lcp可能也可以,甚至可能写起来更快,不过我没试,我最近在练习后缀数组,所以来练手 后缀数组的典型用法之一----------------后缀数组+lcp+二分 思路:1.首先将所有的字符串每读取一个,就将其反转,作为一组,假设其下标为i到j,那么cnt[i]到cnt[j]都标记为一个数字(这个数字意思是第几个读入的字符

后缀数组之最长公共前缀

#include<stdio.h> #define maxn 100 int main() { int rank[maxn],height[maxn],sa[maxn]= {0,3,1,4,2},s[maxn]= {1,2,3,2,3};//s串可以看成abcbc int i,j,k=0; for(i=0; i<5; i++) rank[sa[i]]=i; for(i=0; i<5; i++) { if(rank[i]==0)//当rank[i]=0的时候也就是求height[0]

HDU 1403 Longest Common Substring(后缀数组,最长公共子串)

hdu题目 poj题目 参考了 罗穗骞的论文<后缀数组——处理字符串的有力工具> 题意:求两个序列的最长公共子串 思路:后缀数组经典题目之一(模版题) //后缀数组sa:将s的n个后缀从小到大排序后将 排序后的后缀的开头位置 顺次放入sa中,则sa[i]储存的是排第i大的后缀的开头位置.简单的记忆就是“排第几的是谁”. //名次数组rank:rank[i]保存的是suffix(i){后缀}在所有后缀中从小到大排列的名次.则 若 sa[i]=j,则 rank[j]=i.简单的记忆就是“你排第几”

codevs 1862 最长公共子序列(求最长公共子序列长度并统计最长公共子序列的个数)

题目描述 Description 字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X="x0,x1,-,xm-1",序列Y="y0,y1,-,yk-1"是X的子序列,存在X的一个严格递增下标序列<i0,i1,-,ik-1>,使得对所有的j=0,1,-,k-1,有xij = yj.例如,X="ABCBDAB",Y="BCDB"是X的一个子序

BZOJ 2946: [Poi2000]公共串

Description 最长公共子串,\(n\leqslant 5,l\leqslant 1000\) Solution SAM... 对于同一字符串取max,不用字符串取min Code /************************************************************** Problem: 2946 User: BeiYu Language: C++ Result: Accepted Time:1012 ms Memory:2308 kb ******

BZOJ 3238 [Ahoi2013]差异(后缀自动机)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3238 [题目大意] 给出一个串,设T[i]表示从第i位开始的后缀, 求sum(len(T[i])+len(T[j])-2*lcp(T[i],T[j])) [题解] 根据反串的后缀自动机建立后缀树, 则两点的LCA在自动机中的length就是他们的LCP, 树形DP统计一下即可. [代码] #include <cstdio> #include <algorithm> #i