poj 3415 Common Substrings(後綴數組+單調棧+dp)

題目鏈接:點擊打開鏈接

博主是看了後綴數組那篇論文後,然後做了這道題練手。

論文原文:基本思路是計算A 的所有後綴和B 的所有後綴之間的最長公共前綴的長度,把最長公共前綴長度不小於k 的部分全部加起來。先將兩個字符串連起來,中間用一個沒有出現過的字符隔開。按height 值分組後,接下來的工作便是快速的統計每組中後綴之間的最長公共前綴之和。掃描一遍,每遇到一個B 的後綴就統計與前面的A 的後綴能產生多少個長度不小於k 的公共子串,這裏A 的後綴需要用一個單調的棧來高效的維護。然後對A 也這樣做一次。具體的細節留給讀者思考。

然後在網上搜了好幾篇解題報告,都只提到了用單調棧來維護,而都沒有講清楚具體思路。感覺看得模模糊糊,很是不詳細,花了不少時間在這個題目上,遂寫下了這篇博文,希望大家能給大家帶來幫助(本人語文水平不太好,輕噴)。

首先對於字符串的兩個後綴ijlcp(最長公共子串),長度爲rank[i]+1~rank[j]區間內height[]的最小值(不妨設rank[i]<rank[j])。按照height分組後,需要統計每一個新的後綴與前面的後綴帶來的新的公共前綴,這裏視爲貢獻度,這裏如果是o(n^2)的複雜度的話,明顯超時。所以想到統計新的後綴所帶來的貢獻的時候,能不能用前一個後綴所做的貢獻來加快時間。我們用一個單調遞增的棧來維護數據。

對於每一個新的後綴,如果heigh值大於棧頂元素:

例如:

(這裏Sa[i]表示的字符串是什麼不重要,重要的是動態規劃思維過程~~~~)

Sa[1]:ab

Sa[2]:abc

Sa[3]:abcd

Sa[4]:abcde

若此時算到Sa[4]這個新的後綴和前面所有後綴(Sa[3],Sa[2],Sa[1])能帶來多大貢獻,首先對於(Sa[1]Sa[2])來說,因爲height[4]>height[3](單調棧棧頂元素),根據lcp的計算方法,所以Sa[3]能和(Sa[1]Sa[2])產生的貢獻值就等同於Sa[3]和(Sa[1]Sa[2])所產生的貢獻值,Sa[4]能和Sa[3]產生的貢獻值爲(height[4]-k+1),所以Sa[4]能產生的新的貢獻值=(Sa[3]產生的貢獻值+height[4]-k+1)

如果height值大於棧頂元素值:

Sa[1]:a

Sa[2]:ab

Sa[3]:abc

Sa[4]:abcd

Sa[5]:abcde

Sa[6]:abcdef

Sa[7]:abc

Sa[8]:ab

若此時計算到Sa[7],height[7]<棧頂元素值height[6],若此時還按照上面的求解方法,明顯多算了Sa[5],Sa[6]後面的後綴e,ef,所以這裏要減去Stacksize[i]*(Stack[top]-height[i])這個多算的後綴,Stacksize數組的含義馬上會說到,它是表示這一段Stack[i]這一段壓縮了多少個數。因爲height[7]<棧頂值,但我們要維護一個單調遞增的棧,又因爲lcp是這段區間的最小值,所以後面和它前面的lcp最大長度不會超過height[7],所以以後開始的後綴同這段區間的lcp不會超過height[7],Stacksize[top]表示嚴格遞增的(height[i]<=3&&height[i]>Stack[top-1])區間含有幾個數。後面按照這種規律依次遞推就能出結果了~~~其實這就是一個統計上的Dp~~~~~

然後來說,這個題目就不難了,先用一個獨特的字符連接兩個字符串,求出後綴數組後分情況統計,先求出B的後綴有多少個和A的公共前綴,再統計A的後綴有多少個和B的公共前綴~~~~~~結果就出來了~~~~~~~

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long llt;
const int maxn=200010;
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int ranks[maxn],height[maxn],s[maxn];
char s1[maxn],s2[maxn],str[maxn];
int cmp(int *r,int a,int b,int k)
{
    return r[a]==r[b]&&r[a+k]==r[b+k];
}
void da(char *r,int n,int m)
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)  wsf[i]=0;
    for(i=0;i<n;i++)  wsf[x[i]=r[i]]++;
    for(i=1;i<m;i++)  wsf[i]+=wsf[i-1];
    for(i=n-1;i>=0;i--)  sa[--wsf[x[i]]]=i;
    p=1;
    j=1;
    for(;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++)  wsf[i]=0;
        for(i=0;i<n;i++)  wsf[wv[i]]++;
        for(i=1;i<m;i++)  wsf[i]+=wsf[i-1];
        for(i=n-1;i>=0;i--)  sa[--wsf[wv[i]]]=y[i];
        t=x;
        x=y;
        y=t;
        x[sa[0]]=0;
        for(p=1,i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
    }
}
void calheight(char *r,int n)
{
    int i,j,k=0;
    for(i=0;i<n;i++)  ranks[sa[i]]=i;
    for(i=0;i<n-1;i++)
    {
        if(k) k--;
        j=sa[ranks[i]-1];
        while(r[i+k]==r[j+k]) k++;
        height[ranks[i]]=k;
    }
}
int Stack[maxn],Stacksize[maxn];
llt tot,top;
int main()
{
    int k;
    while(scanf("%d%s%s",&k,s1,s2)&&k)
    {
        int L1=strlen(s1),L2=strlen(s2);
        str[0]='\0';
        strcat(str,s1);
        str[L1]=1;
        str[L1+1]='\0';
        strcat(str,s2);
        int n=L1+L2+1;
        da(str,n+1,128);
        calheight(str,n+1);
        tot=top=0;
        llt sum=0;
        for(int i=2; i<=n; i++)
        {
            if(height[i]<k) top=tot=0;
            else
            {
                int cnt=0;
                if(sa[i-1]<L1) cnt++,tot+=height[i]-k+1;
                while(top>0&&height[i]<=Stack[top-1])
                {
                    top--;
                    tot-=Stacksize[top]*(Stack[top]-height[i]);
                    cnt+=Stacksize[top];
                }
                Stack[top]=height[i];
                Stacksize[top++]=cnt;
                if(sa[i]>L1) sum+=tot;
            }
        }
        tot=top=0;
        for(int i=2; i<=n; i++)
        {
            if(height[i]<k) top=tot=0;
            else
            {
                int cnt=0;
                if(sa[i-1]>L1) cnt++,tot+=height[i]-k+1;
                while(top>0&&height[i]<=Stack[top-1])
                {
                    top--;
                    tot-=Stacksize[top]*(Stack[top]-height[i]);
                    cnt+=Stacksize[top];
                }
                Stack[top]=height[i];
                Stacksize[top++]=cnt;
                if(sa[i]<L1) sum+=tot;
            }
        }
        printf("%lld\n",sum);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章