POJ 2774 Long Long Message(最长公共子串 -初学后缀数组)

后缀数组的两篇神论文:

国家集训队2004论文集 许智磊

算法合集之《后缀数组——处理字符串的有力工具》

很多人的模版都是用论文上的

包括kuangbin的模版:(DA算法)

模版中比较难理解的地方有两点1.按关键词排序 2.把字符串长度增加一位

按关键词排序的意思其实是基数排序中相当把两位数排序时先排个位,再排十位

这里也一样先排后2^k长度的字符串,再排前2^k长度的字符串,最终排成2^(k+1)字符长度的后缀数组sa

把字符串增加一位,是为了让有意义的串的rank从1开始,还有便于后边不用特判越界等细节地方

/*
*suffix array
*倍增算法 O(n*logn)
*待排序数组长度为n,放在0~n-1中,在最后面补一个0
*da(str ,n+1,sa,rank,height, , );//注意是n+1;
*例如:
*n = 8;
*num[] = { 1, 1, 2, 1, 1, 1, 1, 2, $ };注意num最后一位为0,其他大于0
*rank[] = { 4, 6, 8, 1, 2, 3, 5, 7, 0 };rank[0~n-1]为有效值,rank[n]必定为0无效值(关键处)
*sa[] = { 8, 3, 4, 5, 0, 6, 1, 7, 2 };sa[1~n]为有效值,sa[0]必定为n是无效值(关键处)
*height[]= { 0, 0, 3, 2, 3, 1, 2, 0, 1 };height[2~n]为有效值
*
*/
const int MAXN=20010;
int t1[MAXN],t2[MAXN],c[MAXN];//求SA数组需要的中间变量,不需要赋值
//待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
//除s[n-1]外的所有s[i]都大于0,r[n-1]=0
//函数结束以后结果放在sa数组中

bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}

void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
    n++;//注意
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s的最大值很大,可改为快速排序(只改第一轮)
    for(i = 0;i < m;i++) c[i] = 0;
    for(i = 0;i < n;i++) c[x[i] = str[i]]++;
    for(i = 1;i < m;i++) c[i] += c[i-1];
    for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1)
    {
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i = n-j; i < n; i++) y[p++] = i;//后面的j个数第二关键字为空的最小
        for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i = 0; i < m; i++) c[i] = 0;
        for(i = 0; i < n; i++) c[x[y[i]]]++;
        for(i = 1; i < m;i++) c[i] += c[i-1];
        for(i = n-1; i >= 0;i--) sa[--c[x[y[i]]]] = y[i];
        //根据sa和x数组计算新的x数组
        swap(x,y);<span style="white-space:pre">	</span>//小优化
        p = 1; x[sa[0]] = 0;
        for(i = 1;i < n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p >= n) break;<span style="white-space:pre">	</span>//小优化
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;//注意
    for(i = 0;i <= n;i++) rank[sa[i]] = i;
    for(i = 0;i < n;i++)
    {
        if(k) k--;
        j = sa[rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[rank[i]] = k;
    }
}

int rank[MAXN],height[MAXN];
int RMQ[MAXN];
int mm[MAXN];
int best[20][MAXN];

void initRMQ(int n)
{
    mm[0]=-1;
    for(int i=1;i<=n;i++)
        mm[i]=((i&(i-1))==0)? mm[i-1]+1:mm[i-1];
    for(int i=1;i<=n;i++) best[0][i]=i;
    for(int i=1;i<=mm[n];i++)
        for(int j=1;j+(1<<i)-1<=n;j++)
        {
            int a=best[i-1][j];
            int b=best[i-1][j+(1<<(i-1))];
            if(RMQ[a]<RMQ[b]) best[i][j]=a;
            else best[i][j]=b;
        }
}

int askRMQ(int a,int b)
{
    int t;
    t=mm[b-a+1];
    b-=(1<<t)-1;
    a=best[t][a];b=best[t][b];
    return RMQ[a]<RMQ[b]?a:b;
}

int lcp(int a,int b)
{
    a=rank[a];b=rank[b];
    if(a>b) swap(a,b);
    return height[askRMQ(a+1,b)];
}

char str[MAXN];
int r[MAXN];
int sa[MAXN];

int main()
{
    while(scanf("%s",str) == 1)
    {
        int len = strlen(str);
        int n = 2*len + 1;
        for(int i = 0;i < len;i++) r[i] = str[i];
        for(int i = 0;i < len;i++) r[len + 1 + i] = str[len - 1 - i];
        r[len] = 1;
        r[n] = 0;
        da(r,sa,rank,height,n,128);
        for(int i=1;i<=n;i++) RMQ[i]=height[i];
        initRMQ(n);
        int ans=0,st;
        int tmp;
        for(int i=0;i<len;i++)
        {
            tmp=lcp(i,n-i);//偶对称
            if(2*tmp>ans)
            {
                ans=2*tmp;
                st=i-tmp;
            }
            tmp=lcp(i,n-i-1);//奇数对称
            if(2*tmp-1>ans)
            {
                ans=2*tmp-1;
                st=i-tmp+1;
            }
        }
        str[st+ans]=0;
        printf("%s\n",str+st);
    }
    return 0;
}

不想学DC3了,模版感觉更难理解

对于本题求两个串的LCP,把两个串和为一个串,中间加上未出现字符(剔除LCP跨越了两个串的情况),再求后缀数组的height数组。再剔除两个子串的开始字符是属于同一个串的情况就可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 200000+100;
int t1[MAXN],t2[MAXN],c[MAXN];
bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}

void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
    n++;
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s的最大值很大,可改为快速排序
    for(i = 0;i < m;i++) c[i] = 0;
    for(i = 0;i < n;i++) c[x[i] = str[i]]++;
    for(i = 1;i < m;i++) c[i] += c[i-1];
    for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1)
    {
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i = n-j; i < n; i++) y[p++] = i;//后面的j个数第二关键字为空的最小
        for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i = 0; i < m; i++) c[i] = 0;
        for(i = 0; i < n; i++) c[x[y[i]]]++;
        for(i = 1; i < m;i++) c[i] += c[i-1];
        for(i = n-1; i >= 0;i--) sa[--c[x[y[i]]]] = y[i];
        //根据sa和x数组计算新的x数组
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(i = 1;i < n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p >= n) break;
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++) rank[sa[i]] = i;
    for(i = 0;i < n;i++)
    {
        if(k) k--;
        j = sa[rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[rank[i]] = k;
    }
}

int rank[MAXN],height[MAXN];
char str[MAXN];
int r[MAXN];
int sa[MAXN];
int main()
{
    int len1,len2;
    scanf("%s",str);
    len1=strlen(str);
    for(int i=0;i<len1;i++) r[i]=str[i]-'a'+2;
    r[len1]=1;
    scanf("%s",str);
    len2=strlen(str);
    for(int i=0;i<len2;i++) r[len1+1+i]=str[i]-'a'+2;
    int n=len1+len2+1;
    r[n]=0;
    da(r,sa,rank,height,n,30);
    int ans = 0;
    for(int i = 2; i < n; i ++)
        if((sa[i] < len1 && sa[i-1] > len1) || (sa[i-1] < len1 && sa[i] > len1))
        {
            ans = max(ans, height[i]);
        }
    printf("%d\n", ans);
    return 0;
}
时间: 2024-10-07 05:30:43

POJ 2774 Long Long Message(最长公共子串 -初学后缀数组)的相关文章

POJ 2774 Long Long Message (最长公共子串)

Long Long Message Time Limit: 4000MS   Memory Limit: 131072K Total Submissions: 27062   Accepted: 11010 Case Time Limit: 1000MS Description The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes to him these days

POJ 3294 Life Forms [最长公共子串加强版 后缀数组 &amp;&amp; 二分]

题目:http://poj.org/problem?id=3294 Life Forms Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 18549   Accepted: 5454 Description You may have wondered why most extraterrestrial life forms resemble humans, differing by superficial traits s

【poj1226-出现或反转后出现在每个串的最长公共子串】后缀数组

题意:求n个串的最长公共子串,子串出现在一个串中可以是它的反转串出现.总长<=10^4. 题解: 对于每个串,把反转串也连进去.二分长度,分组,判断每个组. 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 using namespace std; 6 7 const int N=2*21000; 8 int n,sl,cl,c[N],rk

cogs249 最长公共子串(后缀数组 二分答案

http://cogs.pro:8080/cogs/problem/problem.php?pid=pxXNxQVqP 题意:给m个单词,让求最长公共子串的长度. 思路:先把所有单词合并成一个串(假设长度是n,包含分隔符),中间用不同符号分隔,求出high[i](表示rk为i的和rk为i+1的后缀的最长公共前缀),然后二分答案ans,对于rk从1扫到n,如果有一段连续的rk值使得high[rk]>=ans且这段的串盖满了每个单词块,那么ans成立,即最终答案大于ans. #include <a

POJ 3080 Blue Jeans(KMP 最长公共子串)

Blue Jeans Description The Genographic Project is a research partnership between IBM and The National Geographic Society that is analyzing DNA from hundreds of thousands of contributors to map how the Earth was populated. As an IBM researcher, you ha

【wikioi】3160 最长公共子串(后缀自动机)

http://codevs.cn/problem/3160/ sam的裸题...(之前写了spoj上另一题sam的题目,但是spoj被卡评测现在还没评测完QAQ打算写那题题解时再来详细介绍sam的....那就再等等吧. 求两个串的lcs话,就是先建立a串的sam,然后用b的字串去匹配a中. 因为sam中每个状态的len对应最长子串,因此自动机不断trans匹配时,如果没找到下一个点,那么在parent树的祖先中找是否还有子串可以更新(因为祖先的max比这个节点小,且都包含当前状态的right,所

POJ 3261 可重叠的 k 次最长重复子串【后缀数组】

这也是一道例题 给定一个字符串,求至少出现 k 次的最长重复子串,这 k 个子串可以重叠.算法分析:这题的做法和上一题差不多,也是先二分答案,然后将后缀分成若干组.不同的是,这里要判断的是有没有一个组的后缀个数不小于 k.如果有,那么存在k 个相同的子串满足条件,否则不存在.这个做法的时间复杂度为 O(nlogn). Source Code: //#pragma comment(linker, "/STACK:16777216") //for c++ Compiler #include

【POJ 2774】Long Long Message 最长公共子串

还是模板啊,手残&&打成||查错查了1h+TAT #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int N = 2000003; int t1[N], t2[N], c[N]; void st(int *x, int *y, int *sa, int n, int m) { int i;

POJ 1458 - Common Subsequence(最长公共子串)

此文为博主原创题解,转载时请通知博主,并把原文链接放在正文醒目位置. 题目链接:http://poj.org/problem?id=1458 AC代码: 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 using namespace std; 7 int ans,lena,lenb,f[201