您的位置:首页 > 其它

poj2774 后缀数组

2016-02-19 22:46 239 查看
/*
poj2774
题意:
给两个字符串(长度不超过100000),求最长公共子串
用我学过的平常的暴力或kmp的方法都会超时,所以刚好可以用来
当做后缀数组的入门题
在下边借用一些论文里的知识再加上自己的一些理解
一、后缀数组的实现
后缀:就是从某个位置i开始到整个字符串末尾的特殊的字符串,用suffix(i)表示

大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也
就是对于两个字符串u、v,令i 从1 开始顺次比较u[i]和v[i],如果
u[i]=v[i]则令i 加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v
(也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍比较不出结果,那
么若len(u)<len(v) 则认为u<v , 若len(u)=len(v) 则认为u=v , 若
len(u)>len(v)则u>v。

后缀数组:后缀数组SA 是一个一维数组,它保存1..n 的某个排列SA[1],
SA[2],……,SA
,并且保证Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将S 的n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入SA 中。

名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。

求后缀数组与名次数组的方法
有2路倍增算法和3DC3算法两种,第一种方法,容易实现容易理解,但是没第二种方法更高效

height 数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公
共前缀,也就是排名相邻的两个后缀的最长公共前缀。

定义h[i]=height[rank[i]],也就是suffix(i)和在它前一名的后缀的最长公共前缀。
h 数组有以下性质:h[i]≥h[i-1]-1 证明省略,感兴趣可以看论文,所以按照
h[1],h[2]...h
的顺序求height[]会是时间上得到优化

后缀数组的好多应用都和sa[],height[],rank[]有关
对本题求最长公共子串,即可把两个字符串合并,求height得最大值
是要考虑公共前缀必须来自不同的字符串,所以要在中间加上一个特殊字符,
并且判断是否来自不同的字符串
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>
typedef long long ll;
using namespace std;

const int maxn = 200000 + 5;
int wa[maxn], wb[maxn], wv[maxn], wsf[maxn];
int sa[maxn], Rank[maxn],  height[maxn], s[maxn];
char str1[maxn], str2[maxn];
int cmp(int *r, int a, int b, int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}

void getsa(int *r, int *sa, int n, int m)//在字符串后边加上0,让其代表的后缀排名为0
{                                        //有比较时不越界的用处
    int *x = wa, *y = wb, *t;
    /*二路倍增算法是对每个字符开始的长度为2的k次方的子字符串进行排序,
    求出排名,就是rank值,当2的k次方大于n时就对所有的后缀排好了序
    每一次排序都可以利用上次排好的2的k-1次方的结论来优化,那么长为
    2的k次方的字符串就可以用两个2的k-1次方的rank值来表示,对两位从低到高
    进行基数排序即可
    */
    //首先对长为1的字符串进行基数排序,wsf存储的是相当于前缀和
    //x[]数组相当于临时的rank[]数组
    for (int i = 0; i < m; i++) wsf[i] = 0;//每次分配前清空计数器
    for (int i = 0; i < n; i++) wsf[x[i]=r[i]]++;//统计每个桶中的记录数
    for (int i = 1; i < m; i++) wsf[i] += wsf[i-1];//将x[]中的位置依次分配给每个桶
    for (int i = n-1; i >= 0; i--) sa[--wsf[x[i]]] = i;//将所有桶中记录依次收集到sa中
    for (int j = 1, p = 1; p < n; j*=2, m = p)
    {
        //cout << "kkk" << j << endl;
        //第一次基数排序时,用到上次sa中的结果,y数组存下第一次基数排序的结果
        int i = n - j;
        for (p = 0; i < n; i++) y[p++] = i;//后边的要补上0,所以直接是排名最前的
        for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i]-j;
        for (i = 0; i < n; i++) wv[i] = x[y[i]];//第二次基数排序
        for (i = 0; i < m; i++) wsf[i] = 0;
        for (i = 0; i < n; i++) wsf[wv[i]]++;
        for (i = 1; i < m; i++) wsf[i] += wsf[i-1];
        for (i = n-1; i >= 0; i--) sa[--wsf[wv[i]]] = y[i];
        //x和y数组进行交换,y存下第二次排序后的结果
        //然后求得临时的rank[]数组,p为排的的名次,这里进行一个优化,
        //如果p<n说明排名有相同的则要继续进行比较
        for (t = x, x = y, y = t, x[sa[0]] = 0, i = 1, p = 1; i < n; i++)
        {
            x[sa[i]] = cmp(y, sa[i-1], sa[i], j)? p-1 : p++;
        }
    }
    return;
}

void getheight(int *r, int n)//字符串后边不加0
{
    //h[1],h[2]...h
的顺序求height[]
    //这里赋值Rank[]从1开始,防止下边-1操作越界
    //且求height[1]时刚好和最后的字符串后的0比较
    //也是求height字符串后不加0的的巧妙之处
    for (int i = 1; i <= n; i++) Rank[sa[i]] = i;
    int k = 0;                                  
    for (int i = 0; i < n; i++)
    {
        if (k)k--;
        else k = 0;
        int j = sa[Rank[i]-1];//suffix(i)前一名的后缀字符串的起始位置
        while(r[i+k] == r[j+k])k++;
        height[Rank[i]] = k;
    }
}

int main()
{

    //freopen("input.txt", "r", stdin);
    while(scanf("%s%s", str1, str2) == 2)
    {
        //puts(str1);
        //puts(str2);
        int n = 0;
        int len = strlen(str1);
        for (int i = 0; i < len; i++)
            s[n++] = str1[i]-'a'+1;
        s[n++] = 30;
        len = strlen(str2);
        for (int i = 0; i < len; i++)
            s[n++] = str2[i]-'a'+1;
        s
 = 0;

        getsa(s, sa, n+1, 31);

        getheight(s, n);
        int ans = 0;
        len = strlen(str1);
        for (int i = 2; i <= n; i++)
        {
            if (height[i] > ans)
            {
                //判断起始位置即可
                if (sa[i-1] >= 0 && ((sa[i-1] < len && sa[i] >= len) ||
                    (sa[i] < len && sa[i-1] >= len)))
                    ans = height[i];
            }
        }
        cout << ans << endl;
    }
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: