1. 程式人生 > 實用技巧 >POJ-3415 (字尾陣列+單調棧/並查集)

POJ-3415 (字尾陣列+單調棧/並查集)

題目連結:傳送門

大致題意:現給定字串s和t ,求s和t的長度不小於k的公共子串個數;

題目思路:

  對於s的每一個字尾和t的每一個字尾求lcp,如果匹配出的lcp=x,那麼ans+=x-k+1(x>=k),直接暴力顯然不行,就有了下面的方法:

  將兩個串做連線得到字串str,中間隔一個絕對不會出現的任意字元,比如‘#’,得到str = s + '#' + t 。

  再對str求sa和height(加‘#’的目的是為了讓height陣列的值不超過s和t的長度。例如s=‘aaaaa’,t=‘aaaa’,直接連線的話,顯然會使height的值過大,根據height[i]的定義是排名i和i-1的最長公共字首)

  lcp(i,j) = min ( height[i+1] , height[i+2] , ... , height[j] ) ;

  對於排名為j且屬於t串的字尾,直接求所有的lcp(i,j) 之和,其中i∈s且 i < j ,顯然lcp(i,j) 與 lcp(i,j+1) 是有聯絡的(多了一個數字取min,lcp(i,j) <= lcp(i,j+1) ),因此就沒必要重複計算

  那麼根據排名從小到大進行掃描,並且根據其height值維護一個單調棧(單增棧),單調棧的作用是維護[1,i-1] 中的s對 i 的貢獻

  

  另一種思路,可以直接用並查集來算,也是可行的;

  1
#include<cstdio> 2 #include<cstring> 3 #include<ctype.h> 4 #include<algorithm> 5 #include<functional> 6 #pragma GCC optimize(2) 7 using namespace std; 8 //std::mt19937 rnd(233); 9 typedef long long LL; 10 typedef pair<int,int> pii; 11 typedef pair<LL,LL> pLL;
12 #define pb push_back 13 #define mk make_pair 14 #define fi first 15 #define se second 16 #define ls (i<<1) 17 #define rs (i<<1|1) 18 #define mem(a,b) memset(a,b,sizeof(a)) 19 const int N=1e6+5; 20 const int inf=0x3f3f3f3f; 21 const LL mod=1e9+7; 22 LL read() 23 { 24 LL x=0,f=1; 25 char ch=getchar(); 26 while(!isdigit(ch)){ if(ch=='-') f=-1; ch=getchar(); } 27 while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); } 28 return f*x; 29 } 30 int sa[N],rk[N],x[N],y[N],he[N],c[N],n,m,sta[N],num[N]; 31 char s[N],s1[N]; 32 void SA() 33 { 34 int m=130; 35 for(int i=1;i<=m;i++) c[i]=0; 36 for(int i=1;i<=n;i++) c[x[i]=s[i]]++; 37 for(int i=1;i<=m;i++) c[i]+=c[i-1]; 38 for(int i=n;i;i--) sa[c[x[i]]--]=i; 39 for(int k=1;k<=n;k<<=1) 40 { 41 int tot=0; 42 for(int i=1;i<=m;i++) c[i]=0; 43 for(int i=n-k+1;i<=n;i++) y[++tot]=i; 44 for(int i=1;i<=n;i++) if(sa[i]>k) y[++tot]=sa[i]-k; 45 for(int i=1;i<=n;i++) c[x[i]]++; 46 for(int i=1;i<=m;i++) c[i]+=c[i-1]; 47 for(int i=n;i;i--) sa[c[x[y[i]]]--]=y[i]; 48 swap(x,y); 49 tot=x[sa[1]]=1; 50 for(int i=2;i<=n;i++) 51 x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?tot:++tot; 52 if(tot==n) break; 53 m=tot; 54 } 55 } 56 void gethe() 57 { 58 he[1]=0; 59 for(int i=1;i<=n;i++) rk[sa[i]]=i; 60 for(int i=1,k=0;i<=n;i++) 61 { 62 if(rk[i]==1) continue; 63 if(k) k--; 64 int j=sa[rk[i]-1]; 65 while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++; 66 he[rk[i]]=k; 67 } 68 } 69 int main() 70 { 71 int k; 72 while(scanf("%d",&k)!=EOF&&k) 73 { 74 scanf("%s",s+1); 75 scanf("%s",s1+1); 76 int l1=strlen(s+1),l2=strlen(s1+1); 77 n=l1+l2+1; 78 s[l1+1]='#'; 79 for(int i=1;i<=l2;i++) s[l1+1+i]=s1[i]; 80 SA(); gethe(); 81 //printf("s = %s\n",s+1); 82 //for(int i=1;i<=n;i++) printf("%d%c",sa[i],i==n?'\n':' '); 83 //for(int i=1;i<=n;i++) printf("%d%c",he[i],i==n?'\n':' '); 84 int top=0; 85 LL res=0,ans=0; 86 for(int i=1;i<=n;i++)//對於每一個i算出[1,i-1]對i點的貢獻res, 這一次的res由上一次的res轉移得到 87 { 88 if(he[i]<k) top=res=0; 89 else 90 { 91 num[i]=0; 92 if(sa[i-1]<=l1) num[i]++,res+=he[i]-k+1; 93 while(top>0&&he[sta[top]]>=he[i]) 94 num[i]+=num[sta[top]], 95 res-=num[sta[top]]*(he[sta[top]]-he[i]),top--;//加入了一個更小的,由於之前是直接加上的he[i]-k+1,根據lcp=min(...),對於i就減去之前多加的值 96 sta[++top]=i;//可以理解為把凸起來的山包給削平了 97 if(sa[i]>l1+1) ans+=res; 98 } 99 } 100 for(int i=1;i<=n;i++)//對於每一個i算出[1,i-1]對i點的貢獻res 101 { 102 if(he[i]<k) top=res=0; 103 else 104 { 105 num[i]=0; 106 if(sa[i-1]>l1+1) num[i]++,res+=he[i]-k+1; 107 while(top>0&&he[sta[top]]>=he[i]) 108 num[i]+=num[sta[top]], 109 res-=num[sta[top]]*(he[sta[top]]-he[i]),top--; 110 sta[++top]=i; 111 if(sa[i]<=l1) ans+=res; 112 } 113 } 114 printf("%lld\n",ans); 115 for(int i=1;i<=n;i++) s[i]=0; 116 } 117 return 0; 118 }
View Code