[POJ1743]Musical Theme
題意:給一個數字串,求不可重疊的相似子串,兩個子串$a,b$相似的定義是$a_i-b_i$都相等
昨晚二爺講課,去膜拜一發,學了一下不知道學了多少次也沒懂的後綴數組
後綴數組能將字符串$S$的所有後綴$S_{i\cdots n}=\text{Suffix}(i)$排序,$sa_i$表示排第$i$名的後綴的開頭下標,$rank_i$表示$\text{Suffix}(i)$的排名,$height_i$表示排第$i$名的後綴和排第$i-1$名的後綴的最長公共前綴長度
考慮用倍增求$rank_i$,一開始把所有長度為$1$的字符串排序,然後排序長度為$2$的字符串,以此類推
假設當前要排所有長度為$L$的字符串,因為我們已經知道了所有長度為$\dfrac L2$的字符串的相對大小,所以我們把每個長度為$L$的字符串看成由兩個長度為$\dfrac L2$的字符串拼起來的東西,相當於用(長度為$\dfrac L2$的字符串的相對排名)構成的二元組來表示每一個長度為$L$的字符串
原理很簡單,就是用長度為$\dfrac L2$的字符串之間的關系得出長度為$L$之間的字符串的關系
容易看出當$L=1$時字符串大小就是字母大小,之後每次用上一次得出的$rank$組成二元組並排序
註意到我們是對$rank$組成的二元組排序,這意味著被排序的數$\leq n$,所以我們可以使用計數排序,代碼挺好寫的
int cnt[N],tmp[N]; void sort(int*x){ int i,m=0; for(i=1;i<=n;i++){ m=max(m,x[i]); cnt[x[i]]++; } for(i=1;i<=m;i++)x[i]+=x[i-1]; for(i=n;i>0;i--)tmp[cnt[x[i]]--]=x[i]; for(i=1;i<=n;i++)x[i]=tmp[i]; }
計數排序的原理是統計“有多少個數小於等於這個數”,這裏的自減符號用於處理元素相同的情況,倒過來處理保證了相等元素之間的相對順序不會改變(每處理完一個元素,下次碰到這個元素時它的下標已經減去了$1$)
對二元組的排序也容易解決,先對第二維排序,再對第一維排序(因為計數排序是穩定排序,當排完後第一維相等的連續段的第二維是有序的)
於是我們成功求出了$rank_i$,那麽$sa_i$也易於求得
我們還要求$height_i$,有一個關於它的結論是$height_{rank_i}\geq height_{rank_{i-1}}-1$,證明如下(來自許智磊的論文)
以下證明比較繞,請熟悉$sa,rank,height$的概念再看證明
當$height_{rank_{i-1}}\leq1$,顯然成立
當$height_{rank_{i-1}}\geq2$,先證一堆奇奇怪怪的東西
設$lcp(i,j)=lcp(\text{Suffix}(sa_i),\text{Suffix}(sa_j))(i\leq j)$,它等價於$lcp(\text{Suffix}({sa_{i\cdots j}}))$
因為更多串取最長公共前綴不會讓答案變大,所以如果$k\geq j\geq i$,那麽$lcp(k,j)\geq lcp(k,i)$
設$k=sa_{rank_{i-1}-1}$,即$\text{Suffix}(k)$是$\text{Suffix}(i-1)$的前一位
根據$lcp(\text{Suffix}(k),\text{Suffix}(i-1))=height_{rank_{i-1}}\geq2$,兩邊同時向右偏移$1$位,最長公共前綴$-1$,所以$lcp(\text{Suffix}(k+1),\text{Suffix}(i))=height_{rank_{i-1}}-1$
因為$rank_{k}\lt rank_{i-1}$且$height_{rank_{i-1}}\geq2$,所以不等式兩邊所代表的後綴第一位相同,右移一位不影響比較結果,推出$rank_{k+1}\lt rank_i$,也就是$rank_{k+1}\leq rank_i-1$
因為$rank_i\geq rank_i-1\geq rank_{k+1}$,所以$lcp(rank_i,rank_i-1)\geq lcp(rank_i,rank_{k+1})=lcp(\text{Suffix}(i),\text{Suffix}(k+1))=height_{rank_{i-1}}-1$
最後,$height_{rank_i}=lcp(rank_i,rank_i-1)\geq height_{rank_{i-1}}-1$,於是就證完了
如果按$height_{rank_{1\cdots n}}$這樣的順序來求,那麽它每次先$-1$,然後就只可能增加,這保證了整個求$height$的過程是$O(n)$的
算$height_i$的代碼一定要記好,考場上沒時間來證這種鬼定理
所以我們求出了後綴數組中用處最大的東西:$height_i$,下面用它做題
題目說“相似”,假如對原序列差分,那麽問題就變成了不可重疊最長重復子串
考慮二分,假設當前二分的答案是$k$,如果存在$[l,r]$使任意$i\in[l,r]$都有$height_i\geq k$,那麽這些後綴的$lcp$長度就$\geq k$,我們可以貪心地選取兩個相距最遠的後綴,也就是說如果$\max\left\{sa_{l\cdots r}\right\}-\min\left\{sa_{l\cdots r}\right\}\geq k$,那麽就存在兩個不重疊的長度為$k$的子串
因為差分了,所以最後答案應該$+1$
於是這題就做完了,唉終於填了一個看上去不可填的坑...
為什麽我寫的後綴數組常數這麽鬼大>_<
#include<stdio.h> #include<string.h> int s[100010],rk[200010],sa[100010],cnt[100010],id[100010],height[100010],n; struct pr{ int c[2],id; pr(int a=0,int b=0,int d=0){c[0]=a;c[1]=b;id=d;} }p[100010],q[100010]; bool operator!=(pr a,pr b){return(a.c[0]!=b.c[0])||(a.c[1]!=b.c[1]);} int max(int a,int b){return a>b?a:b;} int min(int a,int b){return a<b?a:b;} void sort(int f){ int i,m; memset(cnt,0,sizeof(cnt)); m=0; for(i=1;i<=n;i++){ cnt[p[i].c[f]]++; m=max(m,p[i].c[f]); } for(i=1;i<=m;i++)cnt[i]+=cnt[i-1]; for(i=n;i>0;i--)q[cnt[p[i].c[f]]--]=p[i]; for(i=1;i<=n;i++)p[i]=q[i]; } void suf(){ int i,l,m; memset(rk,0,sizeof(rk)); for(i=1;i<=n;i++)rk[i]=s[i]; for(l=1;l<=n;l<<=1){ for(i=1;i<=n;i++)p[i]=pr(rk[i],rk[i+l],i); sort(1); sort(0); m=0; for(i=1;i<=n;i++){ if(p[i]!=p[i-1])m++; rk[p[i].id]=m; } } for(i=1;i<=n;i++)sa[rk[i]]=i; l=0; for(i=1;i<=n;i++){ if(l)l--; while(s[i+l]==s[sa[rk[i]-1]+l])l++; height[rk[i]]=l; } } bool check(int k){ int i,mx,mn; for(i=1;i<=n;i++){ if(height[i]>=k){ mx=max(mx,max(sa[i-1],sa[i])); mn=min(mn,min(sa[i-1],sa[i])); }else{ mx=-1; mn=n; } if(mx-mn>=k)return 1; } return 0; } int main(){ int i,l,r,mid,ans; while(1){ scanf("%d",&n); if(n==0)break; for(i=1;i<=n;i++)scanf("%d",s+i); n--; for(i=1;i<=n;i++)s[i]=s[i+1]-s[i]+88; suf(); l=1; r=n>>1; ans=0; while(l<=r){ mid=(l+r)>>1; if(check(mid)){ ans=mid; l=mid+1; }else r=mid-1; } printf("%d\n",(ans<4)?0:(ans+1)); } }
[POJ1743]Musical Theme