[poj 3693]后缀数组+出现次数最多的重复子串

题目链接:http://poj.org/problem?id=3693

枚举长度L,看长度为L的子串最多能重复出现几次,首先,能出现1次是肯定的,然后看是否能出现两次及以上。由抽屉原理,这个子串出现次数>=2,那么必定会覆盖s[0],s[L],s[2L],...中相邻的两个,枚举是哪两个。对于覆盖了这两个的重复子串,它重复的次数就是看这两个后缀向前向后各自最多能匹配到多远。假设向前向后共匹配了长度K,那么重复的次数就是K/L+1。

这里有3个问题.

第一个,为什么先前向后各自匹配就可以了?因为子串长度就是L,枚举的这两个位置的距离也是L,那么这两个位置必定得是相同的。

第二个问题,怎么看向前最多匹配多少?向后的话直接通过height数组即可实现,而向前呢?难道要倒着再做一次后缀数组?(当然也不是不可行)事实上不用这么麻烦,直接考虑我们要求的结果K/L+1。利用整除的特性其实很容易得到这个结果。比如我们向后最多匹配L1。现在我们想知道结果能不能比L1/L+1来的大,怎么办呢?考虑L-L1%L,这个数代表L1至少要加几,才会让结果有所增加。那么显然向前距离在这个数以内的我们都不用检验了,因为即使检验到能匹配也没啥用,对结果没啥影响。所以我们检验一下距离是这个数的两个后缀的匹配长度,如果能匹配就更新一下结果。那么距离在这个数以外的呢?其实也不用检验了,如果说距离在这个数以外的还能让结果增加,那必须得再+L,再+L的话其实不必检验了,因为如果能匹配,在之前的求解(上次的枚举)中已经检验过了。因此,只检验一个位置即可。

第三个问题,怎么得到字典序最小的那一组解。通过sa数组的顺序枚举。不过得稍微优化一下,不能所有长度都尝试,不然会T。

穷举长度 L 的时间是 n,每次计算的时间是 n/L。所以整个做法的时间复杂度是 O(n/1+n/2+n/3+……+n/n)=O(nlogn)。(假设查询最长公共前缀的复杂度是O(1),用rmq预处理可以做到)

 

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

const int MAXN = 100010;
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int wa[MAXN*3],wb[MAXN*3],wv[MAXN*3],wss[MAXN*3];
int c0(int *r,int a,int b)
{
    return r[a] == r[b] && r[a+1] == r[b+1] && r[a+2] == r[b+2];
}
int c12(int k,int *r,int a,int b)
{
    if(k == 2)
        return r[a] < r[b] || ( r[a] == r[b] && c12(1,r,a+1,b+1) );
    else return r[a] < r[b] || ( r[a] == r[b] && wv[a+1] < wv[b+1] );
}
void sort(int *r,int *a,int *b,int n,int m)
{
    int i;
    for(i = 0; i < n; i++)wv[i] = r[a[i]];
    for(i = 0; i < m; i++)wss[i] = 0;
    for(i = 0; i < n; i++)wss[wv[i]]++;
    for(i = 1; i < m; i++)wss[i] += wss[i-1];
    for(i = n-1; i >= 0; i--)
        b[--wss[wv[i]]] = a[i];
}
void dc3(int *r,int *sa,int n,int m)
{
    int i, j, *rn = r + n;
    int *san = sa + n, ta = 0, tb = (n+1)/3, tbc = 0, p;
    r[n] = r[n+1] = 0;
    for(i = 0; i < n; i++)if(i %3 != 0)wa[tbc++] = i;
    sort(r + 2, wa, wb, tbc, m);
    sort(r + 1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m);
    for(p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
        rn[F(wb[i])] = c0(r, wb[i-1], wb[i]) ? p - 1 : p++;
    if(p < tbc)dc3(rn,san,tbc,p);
    else for(i = 0; i < tbc; i++)san[rn[i]] = i;
    for(i = 0; i < tbc; i++) if(san[i] < tb)wb[ta++] = san[i] * 3;
    if(n % 3 == 1)wb[ta++] = n - 1;
    sort(r, wb, wa, ta, m);
    for(i = 0; i < tbc; i++)wv[wb[i] = G(san[i])] = i;
    for(i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
        sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for(; i < ta; p++)sa[p] = wa[i++];
    for(; j < tbc; p++)sa[p] = wb[j++];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
    for(int i = n; i < n*3; i++)
        str[i] = 0;
    dc3(str, sa, n+1, m);
    int i,j,k = 0;
    for(i = 0; i <= n; i++)rank[sa[i]] = i;
    for(i = 0; i < n; i++)
    {
        if(k) k--;
        j = sa[rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[rank[i]] = k;
    }
}

int str[MAXN*3],sa[MAXN*3],rk[MAXN],height[MAXN];
int RMQ[MAXN];
int mm[MAXN];
int best[20][MAXN];
void initRMQ(int n)
{
    mm[0]=-1;
    for(int i=1; i<=n; i++)
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
    for(int i=1; i<=n; i++)best[0][i]=i;
    for(int i=1; i<=mm[n]; i++)
        for(int j=1; j+(1<<i)-1<=n; j++)
        {
            int a=best[i-1][j];
            int b=best[i-1][j+(1<<(i-1))];
            if(RMQ[a]<RMQ[b])best[i][j]=a;
            else best[i][j]=b;
        }
}
int askRMQ(int a,int b)
{
    int t;
    t=mm[b-a+1];
    b-=(1<<t)-1;
    a=best[t][a];
    b=best[t][b];
    return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b)
{
    a=rk[a];
    b=rk[b];
    if(a>b)swap(a,b);
    return height[askRMQ(a+1,b)];
}

char s[MAXN];
int cou;
int l;
int a[MAXN];
int cnt=0;

int main()
{
    int cas=0;
    while (~scanf("%s",s) && s[0]!='#')
    {
        l=strlen(s);
        for (int i=0; i<l; i++) str[i]=s[i]-'a'+1;
        str[l]=0;
        da(str,sa,rk,height,l,30);
//        for (int i=0;i<l;i++) printf("%d ",rk[i]);
        for (int i=1;i<=l;i++) RMQ[i]=height[i];
        initRMQ(l);
        cou=1;
        cnt=0;
        a[cnt++]=1;
        for (int L=1; L<=l/2; L++)
        {
            for (int j=0; j+L<l; j+=L)
            {
                int back=lcp(j,j+L);
//                printf(":%d:\n",back);
                if (back/L+1>cou)
                {
                    cou=back/L+1;
                    cnt=0;
                    a[cnt++]=L;
                }
                else if (back/L+1==cou) a[cnt++]=L;
                int check=L-back%L;
                if (check!=L && j-check>=0)
                {
                    int pre=lcp(j-check,j-check+L);
//                    printf(":%d:\n",pre);
                    if (pre/L+1>cou)
                    {
                        cou=pre/L+1;
                        cnt=0;
                        a[cnt++]=L;
                    }
                    else if (pre/L+1==cou) a[cnt++]=L;
                }
            }
        }
//        printf("::%d\n",cou);
        int ansp=-1;
        int ansl=-1;
//        for (int i=1;i<=l;i++) printf("%d ",sa[i]);
        for (int i=1; i<=l&&ansp==-1; i++)
        {
            int j=sa[i];
            for (int k=0; k<cnt&&a[k]<=(l-j)/cou; k++)
            {
                if (j+a[k]<l && lcp(j,j+a[k])/a[k]+1>=cou)
                {
//                    printf("QAQ\n");
                    ansp=j;
                    ansl=a[k];
                    break;
                }
            }
        }
        s[ansp+ansl*cou]=0;
//        printf("%d %d\n",ansp,ansl);
        printf("Case %d: %s\n",++cas,s+ansp);
    }
    return 0;
}

 

posted @ 2017-08-07 23:44  ACMsong  阅读(979)  评论(0编辑  收藏  举报