• 【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;
    }
    /*
    */
    
    
  • 相关阅读:
    坑爹的PostgreSQL的美元符号(有时需要替换成单引号)
    DataFrame的apply用法
    Pytorch写CNN
    Pytorch分类和准确性评估--基于FashionMNIST数据集
    设置Mac终端、pip、Anaconda、PyCharm共用一套环境
    Python编程基本规范
    【转】动态规划:最长递增子序列Longest Increasing Subsequence
    动态规划--找零钱
    在线编写复杂的数学公式--EdrawMath
    pandas如何逐行需改DataFrame
  • 原文地址:https://www.cnblogs.com/valk3/p/12883509.html
Copyright © 2020-2023  润新知