首页 > 编程语言 > 详细

【Poj-3693】Maximum repetition substring 后缀数组 连续重复子串

时间:2020-05-13 18:05:00      阅读:65      评论:0      收藏:0      [点我收藏+]

POJ - 3693

题意

SPOJ - REPEATS 的进阶版,在这题的基础上输出字典序最小的重复字串。

思路

跟上题一样,先求出最长的重复次数,在求的过程中顺便纪录最多次数可能的长度。

因为sa数组是按照字典序排好的,所以我们顺序遍历sa数组,找到第一个符合的输出即可。

PS:(找bug找了好久,需要在字符串结尾加一个没有出现过的字符)

why 字符串结尾加0

做这题让我知道了为什么之前看到有的后缀数组代码需要在字符串最后加一个0。

我的模板是OI-wiki上的(字符串下标从1开始的),
没有在字符串最后加字符,而且之前做题代码也没错过,可能是之前做过的题目数据没有卡到,
这题只有a和b,很大可能会出问题。

具体原因:

如果不加一个前面没有出现过的字符,那么在求height的时候可能会出问题:
\(while(str[i+k]==str[sa[rk[i]-1]+k]) ++k;\)

上面求\(height\)的代码中并没有判断\(i+k\)以及\(sa[rk[i]-1]+k\)是否越界,
因此两个式子中的一个越界的时候,假如之前的样例存在比当前字符串长的,
并且越界之后还\(str[i+k]\)还和\(str[sa[rk[i]-1]+k]\)相等,这样height数组就错了。

加前面没有出现过的字符,就是为了书写方便,越界之后循环就自己退出了。

为什么要加0呢?

因为他们的代码字符串下标是从0开始,在求sa数组的时候,要加一个字符,
扩展到了下标n,这时如果加的不是0,而是一个>= 字符串中最小字符 的一个字符的话,
那么后缀n就会影响到sa数组的正确性。

而加0,正好使得\(rk[n]==0\)\(sa[0]=n\),后缀0~n-1的排名全在1-n之间。

综上:

字符串下标从1开始,加一个没有出现过的字符就可以。

下标从0开始,加一个<=出现过的最小字符就可以:0

代码

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<vector>
#include<math.h>
#define pb push_back
typedef long long ll;
using namespace std;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 1e5+10;

int sa[N],cnt[N],pos[N],rk[N],oldrk[N],ht[N],n,m;
char str[N];
bool cmp(int a,int b,int k)
{
    return oldrk[a]==oldrk[b]&&oldrk[a+k]==oldrk[b+k];
}
void getsa()
{
    memset(cnt,0,sizeof(cnt));
    m=122;
    for(int i=1; i<=n; ++i) ++cnt[rk[i]=str[i]];
    rk[n+1]=‘c‘;//加之前没有出现过
    for(int i=1; i<=m; ++i) cnt[i]+=cnt[i-1];
    for(int i=n; i; i--) sa[cnt[rk[i]]--]=i;
    for(int k=1; k<=n; k<<=1)
    {
        int num=0;
        for(int i=n-k+1; i<=n; ++i) pos[++num]=i;
        for(int i=1; i<=n; ++i) if(sa[i]>k) pos[++num]=sa[i]-k;
        memset(cnt,0,sizeof(cnt));
        for(int i=1; i<=n; ++i) ++cnt[rk[i]];
        for(int i=1; i<=m; ++i) cnt[i]+=cnt[i-1];
        for(int i=n; i; i--) sa[cnt[rk[pos[i]]]--]=pos[i];
        num=0;
        memcpy(oldrk,rk,sizeof(rk));
        for(int i=1; i<=n; ++i) rk[sa[i]]=cmp(sa[i],sa[i-1],k)?num:++num;
        if(num==n) break;
        m=num;
    }
    for(int i=1; i<=n; ++i)
        rk[sa[i]]=i;
    int k=0;
    for(int i=1; i<=n; ++i)
    {
        if(k) --k;
        while(str[i+k]==str[sa[rk[i]-1]+k]) ++k;
        //下面就是加上越界判断
//        while(i+k<=n&&sa[rk[i]-1]+k<=n&&str[i+k]==str[sa[rk[i]-1]+k])
//            ++k;
        ht[rk[i]]=k;
    }
}
int dp[N][20];
void RMQ()
{
    for(int i=1; i<=n; ++i) dp[i][0]=ht[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)
{
    int k=0;
    while((1<<(k+1))<=(r-l+1)) ++k;
    //int k=int(log(r-l+1.0)/log(2.0));// 比上面慢
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
int lcp(int i,int j)
{
    i=rk[i],j=rk[j];
    if(i>j) swap(i,j);
    return query(i+1,j);
}
int tot,len[N];
int main()
{
    int cas=0;
    while(~scanf("%s",str+1)&&strcmp(str+1,"#"))
    {
        tot=0;
        n=strlen(str+1);
        getsa();
        RMQ();
        printf("Case %d: ",++cas);
        int ans=0;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j+i<=n; j+=i)
            {
                int now=lcp(j,j+i);
                int num=now/i+1;
                int k=j-(i-now%i);
                if(k>0&&lcp(k,k+i)>=i) ++num;
                if(num>ans)
                {
                    ans=num;
                    tot=0;
                    len[tot++]=i;
                }
                else if(num==ans)
                {
                    if(len[tot-1]!=i)
                        len[tot++]=i;
                }
            }
        }
        int flag=0;
        for(int i=1; i<=n; ++i)
        {
            for(int j=0; j<tot; ++j)
            {
                int l=len[j];
                if(lcp(sa[i],sa[i]+l)>=(ans-1)*l)
                {
                    str[sa[i]+ans*l]=‘\0‘;//使用结束符比一个个输出快
                    printf("%s\n",str+sa[i]);
                    flag=1;
                    break;
                }
            }
            if(flag)
                break;
        }
    }
    return 0;
}
/*
*/

【Poj-3693】Maximum repetition substring 后缀数组 连续重复子串

原文:https://www.cnblogs.com/valk3/p/12883509.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!