[后缀数组+枚举] hdu 2459 && poj 3693 Maximum repetition substring

来源:互联网 发布:java aes 编辑:程序博客网 时间:2024/04/30 19:43

题意:求重复次数最多的连续子串,要求输出字典序最小的连续子串。

思路:这做这题之前可以先做做 spoj 687 ,是不需要输出子串的,只求次数。

那么求法就是枚举每一个长度,看看这个长度的子串能连续出现几次。

那么能出现几次怎么求呢。我们假设长度为i,那么如果从fuck[j]开始 长度为i的子串连续出现了。

那么必然 fuck[j]==fuck[j+i],那么出现次数便是 lcp(fuck[j],fuck[j+i)/i+1  因为要加上本身的。

但是会出现一个问题,比如这样一个串 aababababab

实际上最长的长度是2 连续次数是5  串是 ababababab 但是我们枚举的时候并没有枚举到这种情况

我们枚举的答案是7/2+1=4

事实上就是 lcp(fuck[2],fuck[4])=k=7 这样其实多出来了1个长度,那么我们要往左边移动这样一个剩余的长度(i-k%i)

也就是再比较一下  lcp(fuck[2-1],fuck[2-1+2])=8>=k 那么就多出来了一个  就把此时的次数加1!

这样问题就解决了。

然后这道题要求出字典序最小的,那么我们就在过程中记录最多次数的ans所对应的长度i有多少个

然后通过sa数组进行枚举,一旦符合就跳出,便是字典序最小的了!


还有一个很神奇的事情,这题居然poj的数据比hdu的水

我一定不是在黑hdu~~

代码:

#include"cstdlib"#include"cstdio"#include"cstring"#include"cmath"#include"queue"#include"algorithm"#include"iostream"#include"map"using namespace std;#define N 100020int wa[N],wb[N],wv[N],wws[N];int sa[N],ra[N],height[N];int v[N],dp[N][30],Log[N],a[N];char fuck[N];int cmp(int *r,int a,int b,int l){    return r[a]==r[b]&&r[a+l]==r[b+l];}void da(int n,int m){    int i,j,p,*x=wa,*y=wb;    for(i=0; i<m; i++) wws[i]=0;    for(i=0; i<n; i++) wws[x[i]=v[i]]++;    for(i=1; i<m; i++) wws[i]+=wws[i-1];    for(i=n-1; i>=0; i--) sa[--wws[x[i]]]=i;    for(j=1,p=1; p<n; j*=2,m=p)    {        for(i=n-j,p=0; i<n; i++) y[p++]=i;        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++) wws[i]=0;        for(i=0; i<n; i++) wws[wv[i]]++;        for(i=1; i<m; i++) wws[i]+=wws[i-1];        for(i=n-1; i>=0; i--) sa[--wws[wv[i]]]=y[i];        for(swap(x,y),p=1,i=1,x[sa[0]]=0; i<n; i++) x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;    }    return ;}void gethei(int n){    int i,j,k=0;    for(i=1; i<=n; i++) ra[sa[i]]=i;    for(i=0; i<n; i++)    {        if(k) k--;        j=sa[ra[i]-1];        while(v[i+k]==v[j+k])            k++;        height[ra[i]]=k;    }    return ;}void rmqinit(int n){    int m=Log[N]; //log2(N)    int i,j;    for(i=1;i<=n;i++) dp[i][0]=height[i];    for(i=1;i<=m;i++)      //dp的方法构造rmq    {        for(j=1;j+(1<<i)-1<=n;j++)            dp[j][i]=min(dp[j][i-1],dp[j+(1<<i>>1)][i-1]);    }    return ;}int lcp(int a,int b)  //查找rmq求lcp{    a=ra[a];    b=ra[b];    if(a>b) swap(a,b);    a++;                //这个一定要记得    int m=Log[b-a+1];    return min(dp[a][m],dp[b-(1<<m)+1][m]);}int main(){    int cas=1;    Log[0]=-1;    for(int i=1;i<=N;i++) Log[i]=(i&(i-1))?Log[i-1]:Log[i-1]+1;    while(scanf("%s",fuck),strcmp(fuck,"#")!=0)    {        int i,j;        int ans=0,cnt=0;        int len=strlen(fuck);        for(i=0;i<len;i++) v[i]=fuck[i]-'a'+2;        v[len]=0;        da(len+1,50);        gethei(len);        rmqinit(len);        for(i=1;i<=len;i++)   //枚举长度i        {            for(j=0;j<len;j+=i)  //j+=i 大大缩小了复杂度!            {                int k=lcp(j,j+i);                int tep=j-(i-k%i);   //往左偏移                int sum=k/i+1;                if(tep>=0&&k%i!=0&&lcp(tep,tep+i)>=k) sum++;                if(sum>ans)  //a数组存的是能够达成最大连续次数的字符串的长度 改变则cnt赋为0                {                    ans=sum;                    cnt=0;                    a[cnt++]=i;                }                else if(sum==ans) a[cnt++]=i;  //相等则添加            }        }        for(i=1;i<=len;i++)      //sa数组  保证字典序神器  从前往后遍历sa通过a数组枚举长度  符合条件就停止 就是字典序最小        {            for(j=0;j<cnt;j++)            {                int tep=a[j];                if(lcp(sa[i],sa[i]+tep)>=(ans-1)*tep) break;            }            if(j!=cnt) break;        }        printf("Case %d: ",cas++);        for(int k=0;k<ans*a[j];k++) printf("%c",fuck[sa[i]+k]);        puts("");    }    return 0;}


0 0