POJ 3693 Maximum repetition substring(好题)

转自http://blog.csdn.net/acm_cxlove/article/details/7854526

在后缀数组神文中也这题的题解。

比较容易理解的部分就是枚举长度为L,然后看长度为L的字符串最多连续出现几次。

既然长度为L的串重复出现,那么str[0],str[l],str[2*l]……中肯定有两个连续的出现在字符串中。

那么就枚举连续的两个,然后从这两个字符前后匹配,看最多能匹配多远。

即以str[i*l],str[i*l+l]前后匹配,这里是通过查询suffix(i*l),suffix(i*l+l)的最长公共前缀

通过rank值能找到i*l,与i*l+l的排名,我们要查询的是这段区间的height的最小值,通过RMQ预处理

达到查询为0(1)的复杂度

 设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。

即把之前的区间前缀L-M%L即可。

然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。


#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define inf -0x3f3f3f3f
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mem0(a) memset(a,0,sizeof(a))
#define mem1(a) memset(a,-1,sizeof(a))
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;

using namespace std;
const int maxn=100000+100;
char s[maxn],s1[maxn];
int sa[maxn],t[maxn],t2[maxn],c[maxn],n;
//构造字符串s的后缀数组,每个字符值必须为0~m-1
void build_sa(int m){
    int *x=t,*y=t2;
    //基数排序
    for(int i=0;i<m;i++)    c[i]=0;
    for(int i=0;i<n;i++)    c[x[i]=s[i]]++;
    for(int i=1;i<m;i++)    c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        //直接利用sa数组排序第二关键字
        for(int i=n-k;i<n;i++)  y[p++]=i;
        for(int i=0;i<n;i++)    if(sa[i]>=k)    y[p++]=sa[i]-k;
        //基数排序第一关键字
        for(int i=0;i<m;i++)    c[i]=0;
        for(int i=0;i<n;i++)    c[x[y[i]]]++;
        for(int i=1;i<m;i++)    c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        //根据sa和y计算新的x数组
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if(p>=n)
            break;
        m=p;                //下次基数排序的最大值
    }
}
int rank1[maxn],height[maxn];

void getHeight(){
    int i,j,k=0;
    for(i=0;i<=n;i++)    rank1[sa[i]]=i;
    for(i=0;i<n;i++){
        if(k)
            k--;
        int j=sa[rank1[i]-1];
        while(s[i+k]==s[j+k])   k++;
        height[rank1[i]]=k;
    }
}
int dp[maxn][20];

void RMQ_init(){
    for(int i=1;i<=n;i++)
        dp[i][0]=height[i];
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}

int rmq(int ll,int rr){
    int k=0;
    ll=rank1[ll];              //在这个地方总是出错,需要注意的是,height数组里面的值是后缀的字典序,所以在查找的时候,
    rr=rank1[rr];                //需要找到其排名,而不是其坐标.......
    if(ll>rr)
        swap(ll,rr);
    ll++;
    while((1<<(k+1))<=rr-ll+1) k++;
    return min(dp[ll][k],dp[rr-(1<<k)+1][k]);
}

int a[maxn];

void debug(){
    for(int i = 0 ; i <= n ; i ++) printf("%d ",sa[i]);
    printf("\n");
    for(int i = 0 ; i <= n ; i ++) printf("%d ",rank1[i]);
    printf("\n");
    for(int i = 0 ; i <= n ; i ++) printf("%d ",height[i]);
    printf("\n");
}

int main(){
    int case1=1;
    while(scanf("%s",s)!=EOF){
        if(s[0]=='#')
            break;
        printf("Case %d: ",case1++);
        n=strlen(s);
        s[n]='0';
        n++;
        build_sa(144);
        n--;
        getHeight();
        //debug();
        RMQ_init();
        int cnt=0,maxv=0;
        for(int l=1;l<=n/2;l++){
            for(int i=0;i+l<n;i+=l){
                if(s[i]!=s[i+l])    //快了几百ms
                    continue;
                int r=rmq(i,i+l);
                int step=r/l+1;
                int k=i-(l-r%l);    //k可能小于0
                if(k>=0&&r%l){
                    if(rmq(k,k+l)>=r)
                        step++;
                }
                if(step>maxv){
                    cnt=0;
                    maxv=step;
                    a[cnt++]=l;
                }
                else if(step==maxv)
                    a[cnt++]=l;
            }
        }
        int len=-1,st;
        for(int i=1;i<=n&&len==-1;i++){
            for(int j=0;j<cnt;j++){
                int l=a[j];
                if(rmq(sa[i],sa[i]+l)>=(maxv-1)*l){
                    len=l;
                    st=sa[i];
                    break;
                }
            }
        }
        for(int i=st,j=0;j<len*maxv;j++,i++)
            printf("%c",s[i]);
        printf("\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值