POJ3693 - Maximum repetition substring

Portal

Description

给出一个字符串\(s\),求\(s\)的一个循环子串,其循环次数最多。

Solution

容易知道\(\left \lfloor \dfrac{lcp(s_{i..n},s_{i+L..n})}{L} \right \rfloor+1\)就等于从\(s_i\)开始的长度为\(L\)的串的循环次数。
\[\begin{align*} s \quad& cababacbacbacbaca \\ i=2 \quad& .aba............. \\ i+L=4 \quad& ...aba........... \\ i=4 \quad& .....acbacbac.... \\ i+L=7 \quad& ........acbacbac. \\ \end{align*}\]

枚举循环节的长度\(L\)。如果有一个长度为\(L\)的子串连续出现,那么它至少会覆盖\(s_1,s_{L+1},s_{2L+1}...\)中的连续两个。那么我们记录\(s_{tL+1}\)\(s_{(t+1)L+1}\)向前匹配的长度为\(x\),向后匹配的长度为\(y\)。那么循环串可能以\(s_{i-x}..s_{i-x+(y-x)\%L}\)中任意一个作为开头循环\(\dfrac{x+y}{L}+1\)次,用RMQ求出区间最小的\(rnk\)即可更新答案。

Code

//Maximum repetition substring
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int const N=1e5+10;
int n;
char s[N];
int sa[N],rnk[N<<1],h[N];
int cnt[N],tmp[N],rnk1[N<<1];
int rmq[N<<1][20];
void getSA()
{
    memset(rnk,0,sizeof rnk);
    memset(cnt,0,sizeof cnt);
    for(int i=1;i<=n;i++) cnt[s[i]]=1;
    for(int i=1;i<=256;i++) cnt[i]+=cnt[i-1];
    for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
    for(int L=1,k=0;k<n;L<<=1)
    {
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
        k=0; memcpy(rnk1,rnk,sizeof rnk1);
        for(int i=1;i<=n;i++)
        {
            if(rnk1[sa[i]]!=rnk1[sa[i-1]]||rnk1[sa[i]+L]!=rnk1[sa[i-1]+L]) k++;
            rnk[sa[i]]=k;
        }
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rnk[i]==1) {h[1]=k=0; continue;}
        if(k) k--;
        while(s[i+k]==s[sa[rnk[i]-1]+k]) k++;
        h[rnk[i]]=k;
    }
    memset(rmq,0,sizeof rmq);
    for(int i=1;i<=n;i++) rmq[i][0]=h[i];
    for(int k=1;(1<<k)<=n;k++)
        for(int i=1;i<=n;i++)
            rmq[i][k]=min(rmq[i][k-1],rmq[i+(1<<k-1)][k-1]);
}
int lcp(int i,int j)
{
    if(i==j) return n-i+1;
    int x=rnk[i],y=rnk[j],t=0;
    if(x>y) swap(x,y); x++;
    while((1<<t)<=y-x+1) t++; t--;
    return min(rmq[x][t],rmq[y-(1<<t)+1][t]);
}
int ansS,ansT,ansL;
int main()
{
    for(int task=1;true;task++)
    {

    memset(s,0,sizeof s);
    scanf("%s",s+1); if(s[1]=='#') break;
    n=strlen(s+1); getSA();
    ansS=1,ansT=1,ansL=n;
    for(int L=1;L<=n;L++)
        for(int i=1;i+L<=n;i+=L)
        {
            int j=i+L,len=lcp(i,j);
            for(int k=0;i-k>0&&s[i-k]==s[j-k]&&k<L;k++)
            {
                int t=(len+k)/L+1;
                if(t>ansT) ansS=i-k,ansT=t,ansL=L;
                else if(t==ansT&&rnk[i-k]<rnk[ansS]) ansS=i-k,ansT=t,ansL=L;
            }
        }
    printf("Case %d: ",task);
    for(int i=1;i<=ansT*ansL;i++) printf("%c",s[ansS+i-1]);
    puts("");

    }
    return 0;
}

P.S.

我没写RMQ,也能AC原题;但是\(aaaa..aa\)就能hack我这个程序。

转载于:https://www.cnblogs.com/VisJiao/p/POJ3693.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值