POJ3693 Maximum repetition substring

题意:给一个长度100000以内的字符串,求出其中重复次数最多的可重叠子串,如果有多个结果输出字典序最小的。

思路:枚举循环节长度len,对于每个len,枚举串的起点j,每次递增len,求出后缀j与后缀(j+len)的最长公共前缀L,这个只需要求出min(height[j],...,height[j+len]),这个先用一个ST表预处理出来。那么len的循环次数就是L/len+1,这时候再处理j不是循环节起点的情况,我们最多只需要向前枚举len-1位,判断是否能够作为新的循环节起点即可。处理过程过不断更新最大结果,遇到多个结果,用rk比较两个串的字典序即可。

SA和ST表复杂度都是O(N*lgN),枚举循环节长度复杂度O(N+N/2+N/3+...+1)=O(N*lgN)。

说起来有点麻烦,看代码模拟一下就好了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <cstdlib>
#include <set>
#include <map>
#include <vector>
#include <string>

using namespace std;

typedef long long ll;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int mod = 1000000007;

char s[maxn];
int wa[maxn],wb[maxn],wv[maxn],Ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
// 传入参数:str,sa,len+1,ASCII_MAX+1
void da(const char r[],int sa[],int n,int m) {
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++) Ws[i]=0;
    for(i=0; i<n; i++) Ws[x[i]=r[i]]++;//以字符的ascii码为下标
    for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
    for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i;
    //for(int i=0;i<n+1;i++)cout<<sa[i]<<' ';
    for(j=1,p=1; p<n; j*=2,m=p) {
        for(p=0,i=n-j; 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++) Ws[i]=0;
        for(i=0; i<n; i++) Ws[wv[i]]++;
        for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
        for(i=n-1; i>=0; i--) sa[--Ws[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j])?p-1:p++;
            //x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
int sa[maxn],rk[maxn],height[maxn];
// sa,rk,height 下标均从1开始,串下标从1开始计数
// str,sa,len
void calheight(const char *r,int *sa,int n) {
      int i,j,k=0;
      for(i=1; i<=n; i++) rk[sa[i]]=i;
      for(i=0; i<n; height[rk[i++]]=k)
            for(k?k--:0,j=sa[rk[i]-1]; r[i+k]==r[j+k]; k++);
      for(int i=n;i>=1;--i) ++sa[i],rk[i]=rk[i-1];
}
int st[maxn][21], Log[maxn];
void ST(int m) {
    for (int i = 0; i < m; ++i) {
        st[i][0] = height[i];
        Log[i + 1] = log(i + 0.5) / log(2.0);
    }
    Log[1] = 0;
    for (int j = 1; j <= Log[m]; ++j) {
        for (int i = 0; i <= m - (1 << j); ++i) {
            st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int query(int a, int b) {
    int l = rk[a + 1], r = rk[b + 1];
    if (l > r) swap(l, r);
    ++l;
    int k = Log[r - l + 1];
    return min(st[l][k], st[r - (1 << k) + 1][k]);
}
int main() {
    int cas = 0;
    while (~scanf("%s", s) && s[0] != '#') {
        int n = strlen(s);
        s[n] = 0;
        da(s, sa, n + 1, 130);
        calheight(s, sa, n);
        ST(n + 1);
        // ST处理出每个区间内的最小height值,即最长公共前缀
        int l = n / 2, i, j, L, cnt, k;
        int ans = 1, p = sa[1] - 1, len = 1;
        for (i = 1; i <= l; ++i) {  // 枚举循环节长度
            for (j = 0; j + i < n; j += i) {  // 枚举循环节起点
                if (s[j] != s[j + i]) continue;
                L = query(j, j + i);

                int en = j + i + L;
                for (k = 0; k < i; ++k) {  // 向前查找循环节起点
                    int beg = j - k;
                    if (beg < 0 || s[beg] != s[beg + i]) break;
                    cnt = (en - beg) / i; // 根据循环节起点终点计算循环次数
                    if (cnt > ans || (cnt == ans && rk[beg + 1] < rk[p + 1])) {
                        ans = cnt, p = beg, len = cnt * i;
                    }
                }
            }
        }
        printf("Case %d: ", ++cas);
        for (i = 0; i < len; ++i) {
            printf("%c", s[i + p]);
        }
        printf("\n");
    }
    return 0;
}
/*
ccabababcdaabbccaaaa
daabbccaa
daabbccaaaaccabababc
ccabababc
fduhad
bcad
dafdfafr
cccabcabc
ccababababc
zzzzyykkllzz
bbabbabbab
babbabbabbb
bbbabbabbab
ccbabbabbab
#
*/

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值