【训练记录】后缀数组
POJ-1743
题目大意:给定一个字符串,求最长的重复的子串,且两个串不能重叠,且长度至少为5
思路:
题目中的重复,为同时加上减去同一个整数,仍算重复
转化成完全重复来判断,即每个字符为这个字符与它下一个字符的差值,这样可以拿后缀数组求解
判断是否有长度为k的子串是相同的,且不重叠,那么求最优,可以利用二分+判定
考虑利用height数组来求解,对于height值求解,对于排序后的Suffix分组,使每组的间的后缀值都不小于k
那么答案一定在同一组中,判断每组的SA最大和最小是否>=k,如果存在,即为有解,否则无解
CODE:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define maxn 40010 int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int S[maxn]; int SA[maxn]; int n; int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } void DA(int *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for(int i=0; i<m; i++) ws[i]=0; for(int i=0; i<n; i++) ws[x[i]=r[i]]++; for(int i=1; i<m; i++) ws[i]+=ws[i-1]; for(int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1;for(int j=1;p<n;j*=2,m=p) { p=0; for(int i=n-j;i<n;i++) y[p++]=i; for(int i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(int i=0; i<n; i++) wv[i]=x[y[i]]; for(int i=0; i<m; i++) ws[i]=0; for(int i=0; i<n; i++) ws[wv[i]]++; for(int i=1; i<m; i++) ws[i]+=ws[i-1]; for(int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t,p=1,x[sa[0]]=0; for(int i=1; i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; void calheight(int *r,int *sa,int n) { int i,j,k=0; for(i=1;i<=n;i++) rank[sa[i]]=i; for(i=0;i<n;height[rank[i++]]=k) for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++); return; } bool check(int x) { int minn,maxx; minn=maxx=SA[1]; for (int i=2; i<=n; i++) { if (height[i]>=x && i<n) { maxx=max(maxx,SA[i]); minn=min(minn,SA[i]); continue; } if (maxx-minn>=x) return 1; maxx=minn=SA[i]; } return 0; } int main() { while (1) { n=read(); if (!n) break; for (int i=0; i<n; i++) S[i]=read(); for (int i=0; i<n; i++) S[i]=S[i+1]-S[i]+200; n--; S[n]=0; DA(S,SA,n+1,300); calheight(S,SA,n); int l=4,r=n,ans=0; while (l<=r) { int mid=(l+r)>>1; if (check(mid)) ans=mid,l=mid+1; else r=mid-1; } ans++; if (ans<5) puts("0"); else printf("%d\n",ans); } return 0; }
POJ-3621
题目大意:给定一个字符串,求k次最长重复的子串,这里串可以重叠
思路:
后缀数组 求 可重叠的k次最长重复子串
同上题的思想
先二分答案,然后对后缀分组
判断是否存在一个组,其后缀个数>=K,如果存在则存在解,否则不存在
时间复杂度O(nlogn)
CODE:
#include<cstdio> #include<cstring> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define maxn 20002 int n,K; int ws[maxn],wv[maxn],wa[maxn],wb[maxn]; int S[maxn],SA[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(int *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(int *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } inline bool check(int x) { int tmp=0,cnt=0; for (int i=1; i<=n; i++) { if (height[i]<x) {if (cnt>tmp) tmp=cnt;cnt=0;} else if (!cnt) cnt=2; else ++cnt; } if (cnt>tmp) tmp=cnt; if (tmp>=K) return 1; else return 0; } int main() { n=read(),K=read(); for (int i=0; i<n; i++) S[i]=read(); S[n]=0; DA(S,SA,n+1,20001); calheight(S,SA,n); int l=1,r=n,mid; while (l<r) { mid=(l+r+1)>>1; if (check(mid)) l=mid; else r=mid-1; } printf("%d\n",l); return 0; }
URAL-1297
题目大意:给定一个字符串,求出最长的回文串
思路:
后缀数组可以解决
枚举每一个字符,作为中间字符,这里分成回文串字符数为单数,和回文串字符数为双数两种来讨论
那么问题可以转化为:一个后缀和一个反串的后缀的最长公共前缀
做起来很简单,在原串后建反串,中间用一个特殊字符链接,这样问题就可以转化为 现在的串的两个后缀的最长公共前缀
那么求出Height数组后,变成RMQ问题,那么考虑采用ST的方法
CODE:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define maxn 1000100 char S[maxn]; int SA[maxn],len; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int log2[maxn]; int dp[maxn][21]; void ST(int n) { log2[0]=-1; for (int i=1; i<=n; i++) if (i&(i-1)) log2[i]=log2[i-1]; else log2[i]=log2[i-1]+1; for (int i=1; i<=n; i++) dp[i][0]=height[i]; for (int j=1; (1<<j)<=len; j++) for (int i=1; i+(1<<j)-1<=n; i++) dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } int RMQ(int l,int r) { int tmp=log2[r-l+1]; return min(dp[l][tmp],dp[r-(1<<tmp)+1][tmp]); } int LCP(int l,int r) { l=rank[l]; r=rank[r]; if (l>r) swap(l,r); l++; return RMQ(l,r); } int main() { while (scanf("%s",S)!=EOF) { len=strlen(S); int ll=len; S[len]=126; for (int i=0; i<len; i++) S[len+i+1]=S[len-i-1]; len=len*2+1; S[len]=0; DA(S,SA,len+1,200); calheight(S,SA,len); ST(len); int st=0,ans=1; for (int i=0; i<ll; i++) { int odd=LCP(i,len-i-1);//奇数的情况 if ((odd<<1)-1>ans) ans=(odd<<1)-1,st=i-odd+1; int even=LCP(i,len-i);//偶数的情况 if ((even<<1)>ans) ans=even<<1,st=i-even; } if (ans==1) putchar(S[0]); else for (int i=st; i<st+ans; i++) putchar(S[i]); puts(""); } return 0; }
POJ-2406
题目大意:给定字符串,求最大重复次数
思路:
KMP很好想,暴力也很好想,都可A
后缀数组的方法,求出Height后,枚举k
先判断总串长能否整除,再判断Suffix(1)和Suffix(k+1)的LCP是否为n-k;
PS:倍增的后缀数组会被卡,只能用DC3..MDZZ!!!
CODE:
自己TLE的:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 1001000 char S[maxn];int SA[maxn],len; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int minn[maxn]; void prework() { int mid=rank[0]; minn[mid]=maxn; for (int i=mid-1; i>=0; i--) if (minn[i+1]>height[i+1]) minn[i]=height[i+1]; else minn[i]=minn[i+1]; for (int i=mid+1; i<=len; i++) if (minn[i-1]>height[i]) minn[i]=height[i]; else minn[i]=minn[i-1]; } int main() { while (scanf("%s",S)!=EOF&&(S[0]!='.'&&strlen(S)!=1)) { len=strlen(S); S[len]=0; DA(S,SA,len+1,128); calheight(S,SA,len); prework(); int ans=1; for (int i=1; i<=len/2; i++) { if (len%i) continue; if (minn[rank[i]]==len-i) {ans=len/i;break;} } printf("%d\n",ans); } return 0; }
别人DC3 AC版:
#include <stdio.h> #include<string.h> #define maxn 1000001 char c; int r[maxn*3],sa[maxn*3]; int ans[maxn]; char str[maxn*3]; #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],wb[maxn],wv[maxn],ws[maxn]; 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++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i]; return; } void dc3(int *r,int *sa,int n,int m) // r为待匹配数组 n为总长度 m为字符范围 { int i,j,*rn=r+n,*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++]; return; } int rank[maxn],height[maxn]; void calheight(int *r,int *sa,int n) // 求height数组。 { int i,j,k=0; for(i=1;i<=n;i++) rank[sa[i]]=i; for(i=0;i<n;height[rank[i++]]=k) for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++); return; } int RMQ[maxn]; int mm[maxn]; ///int best[20][maxn];//best[i][j] 表示从j开始的长度为2的i次方的一段元素的最小值 /*void initRMQ(int n)///O(Nlogn) 预处理 { int i,j,a,b; for(mm[0]=-1,i=1;i<=n;i++) mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; for(i=1;i<=n;i++) best[0][i]=i; for(i=1;i<=mm[n];i++) for(j=1;j<=n+1-(1<<i);j++) { a=best[i-1][j]; b=best[i-1][j+(1<<(i-1))]; if(RMQ[a]<RMQ[b]) best[i][j]=a; else best[i][j]=b; } return; } int askRMQ(int a,int b)///询问a,b后缀的最长公共前缀 O(1)查询 { 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) { int t; a=rank[a];b=rank[b]; if(a>b) {t=a;a=b;b=t;} return(height[askRMQ(a+1,b)]); } */ int f[maxn];//f[i]表示lcp(0,i); void get_f(int n) { int i,j,mmin; j=rank[0]; mmin=999999999; /*以下2个循环内的代码顺序不同的原因是 i和j的最长公共前缀lcp(rank[i],rank[j])的值应为 rmq(height,rank[i]+1,rank[j]) 注意有个+1 */ for(i=j;i>=1;i--) { f[i]=mmin; mmin=mmin<height[i]?mmin:height[i];//应该包括height[j] } mmin=999999999; for(i=j+1;i<=n;i++) { mmin=mmin<height[i]?mmin:height[i]; //不应该包括height[j] f[i]=mmin; } } int main() { int i,n; while(scanf("%s",str)!=EOF) { n=strlen(str); if(n==1&&str[0]=='.') break; for(i=0;i<n;i++) r[i]=str[i]-'a'+1; r[n]=0; dc3(r,sa,n+1,123);//千万注意+1 calheight(r,sa,n); // initRMQ(n); /* for(i=0; i<n+1; i++) // rank[i] : suffix(i)排第几 printf("rank[%d] = %d\n",i,rank[i]); printf("\n"); for(i=0; i<n+1; i++) // sa[i] : 排在第i个的是谁 printf("sa[%d] = %d\n",i,sa[i]); */ int len; int mmax=0; get_f(n); for(len=1;len<=n;len++) { if(n%len==0) { if(f[rank[len]]==(n-len)) ///注意是rank[len],因为这里在求0和0+len的lcp ,即要求rank[0]到rank[len]之间的最小height值 { mmax=n/len; break; } } } if(mmax!=0) printf("%d\n",mmax); else printf("1\n"); } return 0; }
POJ-3693
题目大意:给定一个字符串,求重复次数最多的连续重复子串
思路:好题!
首先想到枚举长度L,求出长度为L的连续子串最多能连续出现几次;
假设在原字符串中连续出现2次,记这个子字符串为S,那么S肯定包括了字符r[0],r[L],r[L*2],r[L*3],……中的某相邻的两个。所以只须看字符r[L*i]和r[L*(i+1)]往前和往后各能匹配到多远,记这个总长度为K,那么这里连续出现了K/L+1次。
设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。
即把之前的区间前缀L-M%L即可。
然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。
CODE:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 100010 char S[maxn]; int SA[maxn]; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int log2[maxn]; int dp[maxn][21]; void ST(int n) { log2[0]=-1; for (int i=1; i<=n; i++) if (i&(i-1)) log2[i]=log2[i-1]; else log2[i]=log2[i-1]+1; for (int i=1; i<=n; i++) dp[i][0]=height[i]; for (int j=1; (1<<j)<=n; j++) for (int i=1; i+(1<<j)-1<=n; i++) dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } int RMQ(int l,int r) { int tmp=log2[r-l+1]; return min(dp[l][tmp],dp[r-(1<<tmp)+1][tmp]); } int LCP(int l,int r) { l=rank[l]; r=rank[r]; if (l>r) swap(l,r); l++; return RMQ(l,r); } int main() { int t=1; while (scanf("%s",S)&&(S[0]!='#'&&strlen(S)!=1)) { int len=strlen(S); S[len]=0; DA(S,SA,len+1,128); calheight(S,SA,len); ST(len); int cnt=0,maxx=0,a[maxn]; for (int L=1; L<len; L++) for (int i=0; i<len-L; i+=L) { int r=LCP(i,i+L),st=r/L+1,k=i-(L-r%L); if (k>=0&&r%L) if (LCP(k,k+L)>=r) st++; if (st>maxx) maxx=st,cnt=0,a[cnt++]=L; else if (st==maxx) a[cnt++]=L; } int le=-1,sta; for (int i=1; i<=len&&le==-1; i++) { for (int j=0; j<cnt; j++) { int l=a[j]; if (LCP(SA[i],SA[i]+l)>=(maxx-1)*l) {le=l;sta=SA[i];break;} } } printf("Case %d: ",t++); for (int i=sta,j=0; j<le*maxx;j++,i++) putchar(S[i]); puts(""); } return 0; }
POJ-2774 && URAL-1517
题目大意:给定两个字符串,求两个串最长公共子串,输出方案
思路:
相同的两道题,POJ-2774结果为长度,URAL-1517结果为方案..
考虑后缀数组,把第二个串扔到第一个串后,中间以特殊字符链接
求出height数组,根据SA找公共子串,更新最大值即可
这里需要注意,当Suffix(SA[i-1])和Suffix(SA[i])不在同一字符串时,height[i]才满足题意,才能用来更新ans,顺便记录起始位置输出即可
CODE:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 200010 char S[maxn]; int SA[maxn]; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int main() { int ans=0,len,len1; scanf("%s",S); len1=len=strlen(S); S[len]='@'; scanf("%s",S+len+1); len=strlen(S); S[len]='0'; DA(S,SA,len+1,200); calheight(S,SA,len); for (int i=1; i<=len; i++) if ((SA[i]<len1&&SA[i-1]>len1) || (SA[i-1]<len1&&SA[i]>len1)) ans=max(ans,height[i]); printf("%d\n",ans); return 0; }
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 200010 char S[maxn]; int SA[maxn]; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int main() { int ans=0,len,len1; int tt,st; scanf("%d",&tt); scanf("%s",S); len1=len=strlen(S); S[len]='@'; scanf("%s",S+len+1); len=strlen(S); S[len]='0'; DA(S,SA,len+1,200); calheight(S,SA,len); for (int i=1; i<=len; i++) if ((SA[i]<len1&&SA[i-1]>len1) || (SA[i-1]<len1&&SA[i]>len1)) if (ans<height[i]) ans=height[i],st=SA[i-1]; for (int i=st; i<st+ans; i++) putchar(S[i]); puts(""); // printf("%d\n",ans); return 0; }
POJ-3294
题目大意:给定T个串,求存在于大于T个串的最长公共子串
思路:
后缀数组一样可以解决,思路也有之前的类似
把T个串全都衔接在一起,每两个串直接用不同的特殊字符链接,求出height数组后,进行分组
二分,判断每组中的后缀是否存在于不小于T/2个串中,记录答案的首位值即可;
PS:这题不能乱memset,memset多了是会TLE的,理性区别数组的大小,和memset的多少(蟹蟹CA学长)
CODE:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 200010 int S[maxn];int SA[maxn],minl,T; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(int *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(int *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } bool visit[1010];int pos[maxn]; bool check(int x,int n) { int cnt=0; memset(visit,0,sizeof(visit)); for (int i=2; i<=n; i++) { if (height[i]<x) {memset(visit,0,sizeof(visit));cnt=0;continue;} if (!visit[pos[SA[i-1]]]) visit[pos[SA[i-1]]]=1,cnt++; if (!visit[pos[SA[i]]]) visit[pos[SA[i]]]=1,cnt++; if (cnt>T/2) return 1; } return 0; } void output(int x,int n) { int cnt=0,mark=0; memset(visit,0,sizeof(visit)); for (int i=2; i<=n; i++) { if (height[i]<x) {memset(visit,0,sizeof(visit)); cnt=0,mark=0; continue;} if (!visit[pos[SA[i-1]]]) visit[pos[SA[i-1]]]=1,cnt++; if (!visit[pos[SA[i]]]) visit[pos[SA[i]]]=1,cnt++; if (cnt>T/2&&!mark) { for (int j=0; j<x; j++) putchar(S[SA[i]+j]+'a'-1); puts(""); mark=1; } } } int main() { while (scanf("%d",&T)!=EOF&&T) { int len=0,ch=28,ans=0; minl=maxn; for (int i=0; i<T; i++) { char s[1100]; scanf("%s",s); if (strlen(s)<minl) minl=strlen(s); for (int j=0; s[j]; j++) S[len]=s[j]-'a'+1,pos[len]=i,len++; S[len]=ch; pos[len]=ch; ch++; len++; } S[len]=0; DA(S,SA,len+1,ch); calheight(S,SA,len); int l=0,r=minl; while (l<=r) { int mid=(l+r)>>1; if (check(mid,len)) l=mid+1,ans=mid; else r=mid-1; } if (!ans) puts("?"),puts(""); else output(ans,len),puts(""); } return 0; }
POJ-3415
题目大意:给出两个串,求大于K的公共子串个数(允许重复)
思路:好题!
基本思路是计算A的所有后缀和B的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于k的部分全部加起来。
先将两个字符串连起来,中间用一个没有出现过的字符隔开。按height值分组后,接下来的工作便是快速的统计每组中后缀之间的最长公共前缀之和。
扫描一遍,每遇到一个B的后缀就统计与前面的A的后缀能产生多少个长度不小于k的公共子串,这里A的后缀需要用一个单调的栈来高效的维护。
然后对A也这样做一次。
有一些细节:
用一个单调栈,栈中存放两个元素分别height_top与cnt_top,分别表示到i为止的最小height和A串的数目。维护栈中元素的height从顶到底递减:每加入一个元素如果该元素比栈顶元素小则需要将tot中cnt_top个已经累计的height_top全部替换为当前元素的height(lcp是取区间最小值)。
CODE:
#include<cstdio> #include<cmath> #include<cstring> using namespace std; #define maxn 1001000 char S[maxn];int SA[maxn],len; int wa[maxn],wb[maxn],ws[maxn],wv[maxn]; inline int cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } inline void DA(char *r,int *sa,int n,int m) { int p,*x=wa,*y=wb,*t; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[x[i]=r[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[x[i]]]=i; p=1; for (int j=1; p<n; j*=2,m=p) { p=0; for (int i=n-j; i<n; i++) y[p++]=i; for (int i=0; i<n; i++) if (sa[i]>=j) y[p++]=sa[i]-j; for (int i=0; i<n; i++) wv[i]=x[y[i]]; for (int i=0; i<m; i++) ws[i]=0; for (int i=0; i<n; i++) ws[wv[i]]++; for (int i=1; i<m; i++) ws[i]+=ws[i-1]; for (int i=n-1; i>=0; i--) sa[--ws[wv[i]]]=y[i]; t=x,x=y,y=t;p=1;x[sa[0]]=0; for (int i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } int rank[maxn],height[maxn]; inline void calheight(char *r,int *sa,int n) { int k=0; for (int i=1; i<=n; i++) rank[sa[i]]=i; for (int i=0; i<n; height[rank[i++]]=k) {k?k--:0;for (int j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);} } int stack[maxn][2]; int main() { int K; while (scanf("%d",&K)!=EOF && K) { scanf("%s",S); int l1=len=strlen(S); S[len]='@'; scanf("%s",S+len+1); int l2=strlen(S+len+1); len=strlen(S); S[len]=0; DA(S,SA,len+1,200); calheight(S,SA,len); int top=0,cnt=0;long long ans=0,tot=0; for(int i=1;i<=len;i++) { if(height[i]<K) top=0,tot=0; else { cnt=0; if(SA[i-1]<l1) cnt++,tot+=height[i]-K+1; } while(top && height[i]<=stack[top-1][0]) { top--; tot+=(height[i]-stack[top][0])*stack[top][1]; cnt+=stack[top][1]; } stack[top][0]=height[i],stack[top++][1]=cnt; if(SA[i]>l1) ans+=tot; } top=tot=cnt=0; for(int i=1;i<=len;i++) { if(height[i]<K) top=0,tot=0; else { cnt=0; if(SA[i-1]>l1) cnt++,tot+=height[i]-K+1; } while(top && height[i]<=stack[top-1][0]) { top--; tot+=(height[i]-stack[top][0])*stack[top][1]; cnt+=stack[top][1]; } stack[top][0]=height[i],stack[top++][1]=cnt; if(SA[i]<l1) ans+=tot; } printf("%lld\n",ans); } return 0; }
THE END.
一点点感想:
有一些惯用的套路和基本的思路:
有一些问题,可以考虑对原串建反串,利用反串的后缀数组来求解
多串的问题,可以将串链接起来,求后缀数组后,利用各串间的关系求解
有两种比较容易见到的方法:
对height数组分组,二分对每个组进行一些统计;
与RMQ问题相结合,去进行LCP有关的处理;
一些可能需要注意的东西吧:
DA里的m,表示的是基数排序的限制,如果是纯字母,一般可以用128限制,纯数字,就用最大数字限制好了
字符串的处理,可以提前转成数,可能会方便一些(某次用字符狂RE,只是化成数就A了)
处理好len的长度问题,DA中len要多1位,calheight中则不需要,因为这两个本质上不是一个含义的
多串相连接,要注意串与串之间的字符不要重复了,否则可能出现问题
在做题的过程中,应该从后缀间的关系进行入手,模板几乎是不变的,其实重点还是在于对后缀数组一些性质和方法的理解吧