[POJ3693]Maximum repetition substring(后缀数组+RMQ)

题目:

我是超链接

题意:

给定一个字符串,求重复次数最多的连续重复子串,并输出字典序最小的一个。

题解:

先枚举长度 L ,然后求长度为 L 的子串最多能连续出现几次。首先连续出现 1 次是肯定可以的,所以这里只考虑至少 2 次的情况。假设在原字符串中连续出现 2 次,记这个子字符串为 S ,那么 S 肯定包括了字符 r[0], r[L], r[L*2], r[L*3], …… 中的某相邻的两个。

所以先看字符 r[L*i] 和 r[L*(i+1)] 的lcp,意为向后最多能匹配多远,然后往前扩展。但如果有一种情况就是再多一些前面的和后面的才能形成一个新的怎么办呢?我们可以等移动到后面去的时候再往前扩展。记这个总长度为 K ,那么这里连续出现了 K/L+1 次。最后看最大值是多少。

两个后缀的最长公共前缀是它们 height 数组的区间最小值,用rmq实现O(1)查询。
时间复杂度为 O(n/1+n/2+n/3+...+n/n)=O(nlogn)

判断字典序最小怎么办呢?直接用我们求出来的rank判断!

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=100005;
int c[N],X[N],n,sa[N],Y[N],rank[N],height[N],f[N][25],*x,*y;char s[N];
void build_sa() 
{
    int m=300; x=X; y=Y;
    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;
        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];

        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]]&&
                              ((sa[i-1]+k>=n?-1:y[sa[i-1]+k])==(sa[i]+k>=n?-1:y[sa[i]+k]))?p-1:p++;
        if (p>n) break;
        m=p;
    }
}
void build_height()
{
    for (int i=0;i<n;i++) rank[sa[i]]=i;
    height[0]=0;
    int k=0;
    for (int i=0;i<n;i++)
    {
        if (!rank[i]) continue;
        if (k) --k;
        int j=sa[rank[i]-1];
        while (i+k<n && j+k<n && s[i+k]==s[j+k]) ++k;
        height[rank[i]]=k;
    }
}
void rmq()
{
    for (int i=0;i<n;i++) f[i][0]=height[i];
    for (int i=1;i<20;i++)
      for (int j=0;j<n;j++)
        if (j+(1<<i)-1<n) f[j][i]=min(f[j][i-1],f[j+(1<<i-1)][i-1]);
        else break;
}
int lcp(int a,int b)
{
    int l=rank[a],r=rank[b];
    if (r<l) swap(l,r);l++;
    int k=log2(r-l+1);
    return min(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
    int id=0;
    while (scanf("%s",s) && s[0]!='#')
    {
        n=strlen(s);
        build_sa();
        build_height();
        rmq();
        int ans=0,sum,al=0,ar,t0,t1,wz,ll;
        for (int len=1;len*2<=n;len++)
          for (int i=0;len*(i+1)<n;i++)
          {
            t0=i*len; t1=(i+1)*len;
            if (s[t0]!=s[t1]) continue;
            ll=t0+lcp(t0,t1)-1;
            for (int j=0;j<=len-1;j++)
            {
                if (t0-j<0 || s[t0-j]!=s[t1-j]) break;
                wz=t0-j;
                sum=(ll-wz+1)/len+1;
                if (ans<sum || (ans==sum && rank[al]>rank[wz])) ans=sum,al=wz,ar=wz+ans*len-1;
            }  
          }
        printf("Case %d: ",++id);
        if (!ans) printf("%c",s[sa[0]]);
        else for (int i=al;i<=ar;i++) printf("%c",s[i]);
        printf("\n");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值