poj 3693 后缀数组+RMQ

这道题是后缀数组的一个典型应用。求一个串中连续重复次数最多的一个子串。做这道题的时候要把握住,连续重复 和 次数最多这两个关键,才能有突破。

下面的思路是照搬大牛的:(大神不要打我可怜

在后缀数组神文中有这题的题解。

比较容易理解的部分就是枚举长度为L,然后看长度为L的字符串最多连续出现几次。

既然长度为L的串重复出现,那么str[0],str[l],str[2*l]……中肯定有两个连续的出现在字符串中。

那么就枚举连续的两个,然后从这两个字符前后匹配,看最多能匹配多远。

即以str[i*l],str[i*l+l]前后匹配,这里是通过查询suffix(i*l),suffix(i*l+l)的最长公共前缀

通过rank值能找到i*l,与i*l+l的排名,我们要查询的是这段区间的height的最小值,通过RMQ预处理

达到查询为0(1)的复杂度

 设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。

即把之前的区间前缀L-M%L即可。

然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。

 

我的代码如下:

#include<iostream>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define MAXD 100010
#define MAXL 300
using namespace std;
int r[MAXD], Rank[MAXD], height[MAXD];
int sa[MAXD], wa[MAXD], wb[MAXD], WS[MAXL], wv[MAXD]; //WS 的大小是最大字符的大小。其他的是串长大小
//r数组存放字符的值
int cmp(int *p, int x, int y, int l)
{
    return p[x] == p[y] && p[x + l] == p[y + l];
}
void da(int n, int m)      //n为字符串长度,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]];
    for(i = 1; i < m; i ++)
        WS[i] += WS[i - 1];
    for(i = n - 1; i >= 0; i --)
        sa[-- WS[x[i]]] = i;
    for(p = 1, j = 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, x[sa[0]] = 0, p = 1, i = 1; i < n; i ++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1: p ++;
    }
}
void calheight(int n)  //n为串的长度减一
{
    int i, j, k = 0;
    for(i = 1; i <= n; i ++)
        Rank[sa[i]] = i;
    for(i = 0; i < n; height[Rank[i ++]] = k)
        for(k ? -- k : 0, j = sa[Rank[i] - 1]; r[i + k] == r[j + k]; k ++);
}
/*****************以上都是后缀数组模板,下面的是具体问题的处理。******************/
char str[MAXD];
int dp[MAXD][20];
int cnt=0,maxtime=0;
void RMQ(int n){
    for(int i = 1; i <= n; i++)
        dp[i][0] = height[i];
    for(int j = 1; (1<<j)<=n; j++)
        for(int i = 1; i+(1<<j)-1<=n; i++)
            dp[i][j] = min( dp[i][j-1], dp[i+(1<<(j-1))][j-1] );
}
int query(int L,int R){
    if(L>R) swap(L,R); L++;
    int k = (int)floor( log(1.*(R-L+1))/log(2.0) );
    return min( dp[L][k], dp[R-(1<<k)+1][k] );
}
/*
void RMQ(int num) //num为串长减一
{
    int i,j;
    int k=log(num+0.0)/log(2.0);
    for(i=1;i<=num;i++) dp[i][0]=height[i];  //根据具体情况初始化
	for(int j = 1; j<=k; ++j)
		for(int i = 1; i<= num; ++i)
			if(i+(1<<j)-1<=num)
			{
				dp[i][j] = min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
			}
}
int query(int l,int R)
{
    int a=Rank[l],b=Rank[R];
    if(a>b) swap(a,b);
    a++;  //要注意这个细节,例如,sa[1]~sa[4]的最小公用前缀,是height[2]~height[4]的最小值。这样就理解为什么加一了。
    int k=log(double(b-a+1))/log(2.0);
    if(dp[a][k]<=dp[b-(1<<k)+1][k])
    return dp[a][k];
    else return dp[b-(1<<k)+1][k];
} */
int main()
{
    int i,j;
    int n;
    int t=0;
    while(scanf("%s",str))
    {
        if(strcmp(str,"#")==0) break;
        t++;
        printf("Case %d: ",t);
        n=strlen(str);
        for(i=0;i<n;i++)
            r[i]=str[i];
        r[n]=0;
        n++;
        memset(Rank,0,sizeof(Rank));   //一定要重新初始化,否则会产生访问上一次结果的可能。
        da(n,300);  //n为修改后的串长
        calheight(n-1);
        RMQ(n-1);
        cnt=0;
        maxtime=0;
        int a[MAXD];
        for(i=1;i<n-1;i++)
        {
            for(j=0;j+i<n-1;j+=i)
            {
                int M=query(Rank[j],Rank[j+i]);
                int time=M/i+1;
                int temp=j-(i-M%i);
                if(temp>=0&&M%i)
                    if(query(Rank[temp],Rank[temp+i])/i>=time)
                       time++;
                if(time>maxtime)
                {
                    maxtime=time;
                    cnt=0;
                    a[cnt++]=i;
                }
                else if(time==maxtime)
                {
                    a[cnt++]=i;
                }
            }
        }
        if(cnt==0||maxtime==1)
        {
            printf("%c\n", str[sa[1]] );
            continue;
        }
        int start=0,len=-1;
        for(i=1;i<n&&len==-1;i++)
        {
            for(j=0;j<cnt;j++)
            {
                if(query(Rank[sa[i]],Rank[sa[i]+a[j]])>=a[j]*(maxtime-1))
                {
                    start=sa[i];
                    len=a[j];
                    break;
                }
            }
        }
        for(i=start;i<len*maxtime+start;i++) printf("%c",str[i]);
        printf("\n");
    }
    return 0;
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值