ACM暑期集训12

今天学了后缀数组,感觉好难理解,只能搬PPT,粘模板了~~

1)后缀: suffix(i)为从下标i开始的后缀

String = “abcdef”   Suffix(1) = “bcdef”   Suffix(2) = “cdef”

什么是后缀数组?

把一个字符串的所有后缀按字典序进行排序。

后缀数组sa[i]表示排名为i的后缀下标是什么,

rk[i]表示下标为i的后缀排名是多少  rk和sa是一种互逆的关系

                             aabac      sa[1] = 0         rk[0] = 1

                             abac        sa[2] = 1         rk[1] = 2

String = “aabac”    ac           sa[3] = 3          rk[2] = 4

                             bac          sa[4] = 2          rk[3] = 3

                             c              sa[5] = 4          rk[4] = 5

最朴素的:把所有后缀找出来,总共有n个,进行快速排序,复杂度nlogn 但是字符串比较也需要n的复杂度,所以总复杂度n^2logn。

优化:使用倍增和基数排序的方法,复杂度nlogn

2)height数组

Height[i]表示suffix(sa[i])和suffix(sa[i-1])的最长公共前缀, 也就是排名i的后缀和排名i-1的后缀的最长公共前缀

如何求height

首先定义h[i] = height(rk[i])

有一个定理h[i] >= h[i-1] -1

注意这里的i表示字符串的下标,也就是字符串 下标在每次右移时,它的height一定不会突降2 及以上。

一个比较明显的结论:两个排名越靠近的 后缀,相似度越高。

2.代码

#include <bits/stdc++.h>
using namespace std;
//倍增算法 O(n*logn)
//待排序数组长度为n,放在0~n-1中,在最后面补一个0 num[n] = 0
const int MAXN = 10000;
char s[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wd[MAXN],num[MAXN],minl[MAXN][20],n;
//wa,wb是给关键字排序用的临时数组
//wd是基数排序用的临时数组
//把字符串转化成ascii放在num中
//minl用于查询区间最小值

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

int sa[MAXN],rk[MAXN],height[MAXN];
//注意 最后一个值即num[n]一定要比所有的都小
void SA(int *r,int n){//n输入长度+1,SA(str,len+1)
    int *x=wa,*y=wb,m=0;
    //x作为第一关键字的rank
    for (int i=0;i<n;i++) m=max(r[i]+1,m);//取这些数中的最大值,作为基数排序的上限
    for(int i=0; i<m; ++i) wd[i]=0;//清空桶
    for(int i=0; i<n; ++i) ++wd[x[i]=r[i]];//把每位上的值赋给x[i],并且加入桶中

    for(int i=1; i<m; ++i) wd[i]+=wd[i-1];
    //对桶作一个前缀和,这样就知道自己排名的范围
    //比如aaabb,那么wd['a'] = 3 wd['b'] = 2,
    //做一次前缀和之后wd['a'] = 3 wd['b'] = 5,说明a的排名在1-3,b的排名在4-5

    for(int i=n-1; i>=0; --i) sa[--wd[x[i]]]=i;
    //基数排序,排名为--wd[x[i]]的下标为i,因为这里i从n-1开始,所以当出现值相等时,靠近尾部的排名值更大,靠近头部排名值更小

    int p=1;
    for(int j=1; p<n; j<<=1,m=p){
        p=0;//p代表第二关键字的排名值
        for(int i=n-j; i<n; ++i) y[p++]=i;//从n-j开始的第二关键字都为0,所以他们最小,
        for(int i=0; i<n; ++i) if(sa[i]>=j) y[p++]=sa[i]-j;
        //从排名为0的第一关键字开始,现在要把他们当成第二关键字
        //但是下标在0--j-1这些不用作为第二关键字,因为他们前面没有第一关键字
        //这里比较的是第二关键字,但是y存的是第二关键字对应的第一关键字的位置,方便后面处理,所以要sa[i]-j

		for(int i=0; i<n; ++i) wv[i]=x[y[i]];
		//为基数排序做准备,注意这里y[i]表示,第二关键字排名为i的第一关键字的下标k
        //把k作为x的下标,x[y[i]]表示的是,第二关键字排名为i的第一关键字的值
		//比如需要排序的双关键字为92 41 10 20 63 30 72
		//那么y[]=3 4 6 2 1 7 5 即对第二关键字排序后名次递增所对应的序号
		//	  x[]=1 2 3 4 9 7 6 即对第二关键字排序的结果,用第一关键字的值来反映
		// wv[i]=x[y[i]]就是将x[]数组拷贝到wv[]中

        for(int i=0; i<m; ++i) wd[i]=0;//清空桶
        for(int i=0; i<n; ++i) ++wd[wv[i]];//把第一关键字放进去
        for(int i=1; i<m; ++i) wd[i]+=wd[i-1];//同样和上面一样求前缀和,
        for(int i=n-1; i>=0; --i) sa[--wd[wv[i]]]=y[i];//同样进行排序,这里是=y[i],因为i表示的是排名,y[i]才表示位置

		//上一步只求了sa,还没有求rk,所以下面求rk
        swap(x,y); x[sa[0]]=0; p=1;
        for(int i=1; i<n; ++i) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        //求下一轮的第一关键字,也就是rank,如果当前第一二关键字排名相同,那么下一轮的rank也必须相同

        //用p统计不同rk的数量,当所有rk都不相同的时候,排序结束
    }
    for(int i=1; i<n; ++i) rk[sa[i]]=i;
	//rk和sa是互逆的,这样可以直接求出rk

    int k=0;
    for(int i=0; i<n-1; height[rk[i++]]=k){//把结果赋值给height[rk[i]],然后i自增
        if(k)--k;//判断h[i-1]是否为0,0的话算h[i]的时候直接从头比较就可以了
        for(int j=sa[rk[i]-1];r[i+k]==r[j+k];++k);//j是排名
    }
}

void initRMQ()
{
    int l = int(log((double)n)/log(2.0));
    for (int i=1;i<=n;i++) minl[i][0] = height[i];//初始化
    for (int j=1;j<=l;j++)
        for (int i=1;i+(1<<(j-1)) <=n ;i++)
            minl[i][j] = min(minl[i][j-1],minl[i+(1<<(j-1))][j-1]);
}

int askRMQ(int l,int r)
{
    int k=int(log((double)(r-l+1))/log(2.0));
    return min(minl[l][k],minl[r-(1<<k) +1 ][k]);
}

int lcp(int a,int b)//a,b是在num中相对的位置的下标
{
    a=rk[a],b=rk[b];
    if (a>b) swap(a,b);
    return askRMQ(a+1,b);//这里要注意,height[a]是a和a-1比较了,不能算入。
}
//最长回文串
int main()
{
    while (scanf("%s",s)!=EOF)
    {
        int len=strlen(s);
        n=2*len+1;
        for (int i=0;i<len;i++) num[i] = s[i];
        num[len] = 30;
        for (int i=0;i<len;i++)
            num[i+len+1] = s[len-i-1];
        num[n] = 0;
        SA(num,n+1);
        initRMQ();
        int maxlen=0,maxpos=0;
        for (int i=0;i<len;i++)
        {
            int tmp = lcp(n-i,i+1);
            if (tmp*2+1 > maxlen)
            {
                maxlen = tmp*2+1;
                maxpos = i;
            }
            tmp = lcp(n-i-1,i+1);
            if (tmp*2 > maxlen)
            {
                maxlen = tmp*2;
                maxpos = i;
            }
        }
        if (maxlen&1)
            for (int i=maxpos-maxlen/2;i<=maxpos+maxlen/2;i++)
                printf("%c",s[i]);
        else
            for (int i=maxpos-maxlen/2+1;i<=maxpos+maxlen/2;i++)
                printf("%c",s[i]);
        printf("\n");
    }
    return 0;
}

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_41383801/article/details/81395372
今日推荐