poj 3693 Maximum repetition substring(后缀数组好题)

题目:http://poj.org/problem?id=3693
题目大意:就是给你一个字符串,让你找出它的连续的重复次数最多的子串。
思路:罗穗骞论文里的题目,好题!论文地址:http://wenku.baidu.com/view/228caa45b307e87101f696a8.html
先穷举长度L,然后求长度为L的子串最多能连续出现几次。首先连续出现1次是肯定可以的,所以这里只考虑至少2次的情况。假设在原字符串中连续出现2次,记这个子字符串为S,那么S肯定包括了字符r[0],r[L],r[L*2],r[L*3],……中的某相邻的两个。所以只须看字符r[L*i]和r[L*(i+1)]往前和往后各能匹配到多远,记这个总长度为K,那么这里连续出现了K/L+1
次。最后看最大值是多少。穷举长度L的时间是n,每次计算的时间是n/L。所以整个做法的时间复杂度是O(n/1+n/2+n/3+……+n/n)=O(nlogn)。
以上摘自论文。其实我个人感觉这里的关键是怎么样O(1)匹配前面。当我们枚举 i*len 和(i+1)*len 时,有可能这两个点不是
这个重复子串的起点和终点,只是包含。设 p1 = i*len ,p2 = (i+1)*len,先算k = lcp(p1,p2),如果k%len == 0,那么这个
次数肯定就是了,不用管前面,因为前面最多 len-1 个。然后就是 != 0 的情况,怎么搞定前面?设 t = k%len,那么就是说后面多了
t 个匹配的字符,这时前面如果len - t个字符再一样,次数就要 +1,对,这也是唯一的一种次数 +1 的情况,其他情况不可能!所以,
这个时候就只需要算一下lcp(p1-(len-t),p2-(len-t)),比较一下即可。这里还有个地方需要注意:这里只能得出重复的最多次数,
以及这个次数所对应的长度 len,并不能确定位置。所以后面还要再扫描 sa 数组,根据长度和次数,把字典序最小的找出来。
自己做,枚举那里只能想到 +1 这样枚举,这样就是O(n^2)了,想不出这样 O(n*logn)的算法。。 ORZ。。

代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int MAXN = 111111;

char str[MAXN];

int sa[MAXN],rank[MAXN],height[MAXN];

int t[MAXN],t2[MAXN],c[MAXN];

void cal_height(int n)
{
    for(int i = 1;i <= n;i++) rank[sa[i]] = i;
    int k = 0;
    for(int i = 0;i < n;i++)
    {
        if(k) k--;
        int j = sa[rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[rank[i]] = k;
    }
}

void da(int n,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] = str[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]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k] ? p-1 : p++;
        if(p >= n) break;
        m = p;
    }
    cal_height(n-1);
}

int d[MAXN][20];

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

int rmq(int a,int b)
{
    int k = 0;
    while((1<<(k+1)) < (b-a+1)) k++;
    return min(d[a][k],d[b-(1<<k)+1][k]);
}

int lcp(int a,int b)
{
    a = rank[a],b =rank[b];
    if(a > b) swap(a,b);
    return rmq(a+1,b);
}

int main()
{
    int cas = 0;
    while(~scanf("%s",str))
    {
        if(str[0] == '#') break;
        int n = strlen(str);
        printf("Case %d: ",++cas);
        da(n+1,128);
        rmq_init(n);
        int ans_time = 0;
        vector <int> ans;
        for(int len = 1;len <= n;len++)
        {
            for(int i = 0;i+len < n;i += len)
            {
                int j = i+len;
                int k = lcp(i,j);
                int tmp = k/len+1;
                int ii = i-(len-k%len);
                if(ii >=0 && k%len != 0)
                {
                    if(lcp(ii,ii+len) > k)
                        tmp++;
                }
                if(tmp > ans_time)
                {
                    ans_time = tmp;
                    ans.clear();
                    ans.push_back(len);
                }
                else if(tmp == ans_time)
                {
                    ans.push_back(len);
                }
            }
        }
        //printf("ti = %d\n",ans_time);
        //for(int i = 0;i < ans.size();i++)
            //printf("i = %d,ans = %d\n",i,ans[i]);
        int ok = 0;
        int ans_pos = 0,ans_len = 1;
        for(int i = 1;i <= n && !ok;i++)
        {
            for(int j = 0;j < ans.size();j++)
            {
                if(sa[i]+ans[j] >= n) break;
                if(lcp(sa[i],sa[i]+ans[j]) >= (ans_time-1)*ans[j])
                {
                    ans_pos = sa[i];
                    ans_len = ans[j]*ans_time;
                    ok = 1;
                    break;
                }
            }
        }
        for(int i = 0;i < ans_len;i++)
            printf("%c",str[ans_pos+i]);
        puts("");
    }
    return 0;
}

/*
bacbacb
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值