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

POJ - 3693

题意

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

思路

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

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

why 字符串结尾加0

我懵了,看不懂论文中的解释(下图)
Snipaste_2020-05-18_18-44-51
论文中的解释是说 这样搞,在cmp函数中就不用加越界判断。(我之前也好奇为啥cmp中不用加越界。。。)

下面解释是我自己的理解,不一定准确

原因:

如果不加一个前面没有出现过的字符,那么在求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]];
    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);
        str[n+1]='c';
        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]='';//使用结束符比一个个输出快
                    printf("%s
",str+sa[i]);
                    flag=1;
                    break;
                }
            }
            if(flag)
                break;
        }
    }
    return 0;
}
/*
*/

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