1. 程式人生 > >POJ 3415 Common Substrings(長度不小於K的公共子串的個數+後綴數組+height數組分組思想+單調棧)

POJ 3415 Common Substrings(長度不小於K的公共子串的個數+後綴數組+height數組分組思想+單調棧)

3*3 直接 math break can type strings 需要 bre

http://poj.org/problem?id=3415

題意:
求長度不小於K的公共子串的個數。

思路:
好題!!!拉丁字母讓我Wa了好久!!單調棧又讓我理解了好久!!太弱啊!!

最簡單的就是暴力枚舉,算出LCP,那麽這個LCP對答案的貢獻就是$x-k+1$。

我們可以將height進行分組,大於等於k的在同一組,如果兩個後綴的最長公共子串>=k,那麽它們肯定在同一個組內。現在從頭開始掃,每遇到A的後綴時,就統計一下它和它前面的B的後綴能組成多少長度>=k的公共子串,然後再反過來處理B的後綴即可,一共需要掃兩遍。但是這樣的時間復雜度是O(n^2),是行不通的。

因為兩個後綴的LCP是它們之間的最小height值,所以可以維護一個自底向上遞增的單調棧,如果有height值小於了當前棧頂的height值,那麽大於它的那些只能按照當前這個小的值來計算。這樣分別處理兩次,一次處理A的後綴,一次處理B的後綴。需要記錄好棧裏的和以及每個組內的後綴數。

  1 #include<iostream>
  2 #include<algorithm>
  3 #include<cstring>
  4 #include<cstdio>
  5 #include<vector>
  6 #include<stack>
  7 #include<queue>
  8 #include<cmath>
  9 #include<map>
 10 #include<set>
 11 using namespace std;
12 typedef long long ll; 13 typedef pair<int,int> pll; 14 const int INF = 0x3f3f3f3f; 15 const int maxn=2*1e5+5; 16 17 int n; 18 char s[maxn],s1[maxn]; 19 int sa[maxn],t[maxn],t2[maxn],c[maxn]; 20 int Rank[maxn],height[maxn]; 21 int sta[maxn],cnt[maxn]; 22 23 void build_sa(int
m) 24 { 25 int *x=t,*y=t2; 26 //基數排序 27 for(int i=0;i<m;i++) c[i]=0; 28 for(int i=0;i<n;i++) c[x[i]=s[i]]++; 29 for(int i=1;i<m;i++) c[i]+=c[i-1]; 30 for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; 31 for(int k=1;k<=n;k<<=1) 32 { 33 int p=0; 34 //直接利用sa數組排序第二關鍵字 35 for(int i=n-k;i<n;i++) y[p++]=i; 36 for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; 37 //基數排序第一關鍵字 38 for(int i=0;i<m;i++) c[i]=0; 39 for(int i=0;i<n;i++) c[x[y[i]]]++; 40 for(int i=1;i<m;i++) c[i]+=c[i-1]; 41 for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; 42 //根據sa和y計算新的x數組 43 swap(x,y); 44 p=1; 45 x[sa[0]]=0; 46 for(int i=1;i<n;i++) 47 x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++; 48 if(p>=n) 49 break; 50 m=p; //下次基數排序的最大值 51 } 52 } 53 54 void getHeight(int n) 55 { 56 int i,j,k=0; 57 for(i=1;i<=n;i++) Rank[sa[i]]=i; 58 for(i=0;i<n;i++) 59 { 60 if(k) k--; 61 int j=sa[Rank[i]-1]; 62 while(s[i+k]==s[j+k]) k++; 63 height[Rank[i]]=k; 64 } 65 } 66 67 int main() 68 { 69 int k; 70 //freopen("in.txt","r",stdin); 71 while(~scanf("%d",&k)) 72 { 73 if(!k) break; 74 scanf("%s%s",s,s1); 75 int len1 = strlen(s); 76 int len2 = strlen(s1); 77 s[len1]=@; 78 for(int i=0;i<len2;i++) s[len1+1+i]=s1[i]; 79 s[len1+len2+1]=*; 80 s[len1+len2+2]=\0; 81 n=strlen(s); 82 build_sa(300); 83 getHeight(n-1); 84 85 ll sum=0,ans=0; //sum代表棧裏對答案的貢獻值,cnt數組記錄的是一個組內的後綴數 86 int top=0; 87 for(int i=1;i<n;i++) //處理A和它前面的B的公共前綴 88 { 89 if(height[i]<k) {top=0;sum=0;continue;} 90 int num=0; 91 while(top && sta[top]>height[i]) 92 { 93 sum-=(ll)(sta[top]-k+1)*cnt[top]; //在這兒比height[i]大的都要變成height[i],所以先減去先前加上去的 94 sum+=(ll)(height[i]-k+1)*cnt[top]; //乘cnt[top]的原因是可能有多個合並成一個了,比如4,5,3,那麽4和5都只能以3來計算 95 num+=cnt[top]; //那麽此時這三個可以合並在一起,cnt個數就是3,和就是3*3=9 96 top--; 97 } 98 sta[++top]=height[i]; 99 if(sa[i-1]>len1) 100 { 101 sum+=(ll)height[i]-k+1; 102 cnt[top]=num+1; 103 } 104 else cnt[top]=num; 105 if(sa[i]<len1) 106 ans+=sum; 107 } 108 109 sum=0,top=0; 110 for(int i=1;i<n;i++) //處理B和它前面的A的公共前綴 111 { 112 if(height[i]<k) {top=0;sum=0;continue;} 113 int num=0; 114 while(top && sta[top]>height[i]) 115 { 116 sum-=(ll)(sta[top]-k+1)*cnt[top]; 117 sum+=(ll)(height[i]-k+1)*cnt[top]; 118 num+=cnt[top]; 119 top--; 120 } 121 sta[++top]=height[i]; 122 if(sa[i-1]<len1) 123 { 124 sum+=(ll)height[i]-k+1; 125 cnt[top]=num+1; 126 } 127 else cnt[top]=num; 128 if(sa[i]>len1) 129 ans+=sum; 130 } 131 printf("%I64d\n" , ans); 132 } 133 return 0; 134 }

POJ 3415 Common Substrings(長度不小於K的公共子串的個數+後綴數組+height數組分組思想+單調棧)