題目鏈接:點擊打開鏈接
博主是看了後綴數組那篇論文後,然後做了這道題練手。
論文原文:基本思路是計算A 的所有後綴和B 的所有後綴之間的最長公共前綴的長度,把最長公共前綴長度不小於k 的部分全部加起來。先將兩個字符串連起來,中間用一個沒有出現過的字符隔開。按height 值分組後,接下來的工作便是快速的統計每組中後綴之間的最長公共前綴之和。掃描一遍,每遇到一個B 的後綴就統計與前面的A 的後綴能產生多少個長度不小於k 的公共子串,這裏A 的後綴需要用一個單調的棧來高效的維護。然後對A 也這樣做一次。具體的細節留給讀者思考。
然後在網上搜了好幾篇解題報告,都只提到了用單調棧來維護,而都沒有講清楚具體思路。感覺看得模模糊糊,很是不詳細,花了不少時間在這個題目上,遂寫下了這篇博文,希望大家能給大家帶來幫助(本人語文水平不太好,輕噴)。
首先對於字符串的兩個後綴i,j的lcp(最長公共子串),長度爲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;
}