【GDOI2017模拟8.12】字符串

Description

给出一个长度为n的字符串,求所有连续重复子串中最大的重复次数。
n<=50000 数据组数<=20

Solution

这种题第一眼就应该想到SA。。。
然而SA并不是主要算法。一会再说。
我们枚举重复子串的长度l。那么很显然,这个子串会包含S[0],S[l],S[2l]…中的至少一个点。
我们枚举开头i=0,l,2l,3l….
然后求出k=lcp(i,i+l)。
这里的lcp(x,y)表示以x和y开头的后缀的lcp,可以用SA加上区间最小值求出。
那么重复次数就应该是(k/l+1)除法下整。
但是,开头不一定就是这些点,可能往左移一点,那么左移的距离就是(l-k%l)
然后再求出重复次数,取最大值就好了。
注意SA的常数。

Code

#include<cmath>
#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 50005
using namespace std;
int n,ty,ans,ws[N],x[N],y[N],sa[N],rank[N],mi[15],f[N][15];
char s[N];
int min(int x,int y) {if (x<y) return x;else return y;}
int max(int x,int y) {if (x>y) return x;else return y;}
void tsort() {
    int mx=0;
    fo(i,1,n) mx=max(mx,x[y[i]]);
    fo(i,1,mx) ws[i]=0;
    fo(i,1,n) ws[x[y[i]]]++;
    fo(i,1,mx) ws[i]+=ws[i-1];
    fd(i,n,1) sa[ws[x[y[i]]]--]=y[i];
}
void getsa() {
    fo(i,1,n) y[i]=i,sa[i]=0;tsort();
    for(int j=1;j<=n;j*=2) {
        int k=0;fo(i,n-j+1,n) y[++k]=i;
        fo(i,1,n) if (sa[i]>j) y[++k]=sa[i]-j;
        tsort();
        fo(i,1,n) y[i]=x[i],x[i]=0;
        x[sa[1]]=k=1;
        fo(i,2,n) {
            if (y[sa[i-1]]!=y[sa[i]]||y[sa[i-1]+j]!=y[sa[i]+j]) k++;
            x[sa[i]]=k;
        }
        if (k==n) break;
    }
    fo(i,1,n) rank[sa[i]]=i;
}
void getheight() {
    int k=0;
    fo(i,1,n) {
        if (k) k--;
        int j=sa[rank[i]-1];
        while (i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
        f[rank[i]][0]=k;
    }
}
int lcp(int x,int y) {
    x=rank[x];y=rank[y];int k;
    if (x>y) k=x,x=y,y=k;x++;
    int z=log(y-x+1)/log(2);
    return min(f[x][z],f[y-mi[z]+1][z]);
}
int main() {
    freopen("string9.in","r",stdin);
    mi[0]=1;fo(i,1,14) mi[i]=mi[i-1]*2;
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d",&n);scanf("%s",s+1);
        fo(i,1,n) x[i]=s[i]-'a';
        getsa();getheight();ans=1;
        fo(j,1,14) fo(i,1,n-mi[j-1]) 
        f[i][j]=min(f[i][j-1],f[i+mi[j-1]][j-1]);
        fo(l,1,n/2) fo(i,0,n/l-1) {
            if ((i+1)*l+1>n||i*l+1>n) continue; 
            if ((n-i*l)/l+1<=ans) continue;
            int k=lcp(i*l+1,(i+1)*l+1),r=l-k%l;
            if (i*l+1-r>0) k=lcp(i*l+1-r,(i+1)*l+1-r);
            ans=max(ans,k/l+1);
        }
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值