題目大概是給n個數組成的串,求是否有多個“相似”且不重疊的子串的長度大於等於5,兩個子串相似當且僅當長度相等且每一位的數字差都相等。
這題是傳說中樓教主男人八題之一,雖然已經是用后綴數組解決不可重疊最長重復子串的經典題了。。但其實沒那么簡單,題目數據不強,網上一些代碼都是不正確的。
- 首先把問題轉化成重復子串的問題:把原串每一位都與前一位相減。這樣得出的新串如果有兩個長度為n的子串相同,那么它們對應在原串的長度n+1的子串也就相似。
- 所以接下來要求的就是這個新串不可“重疊”最長重復子串——問題就在這兒,這不只是要求不可重疊,還要求兩個子串要隔至少一個位置,因為如果兩個子串靠在一起這樣反應到原串那兩個子串各自的首尾是重合的。
比如數據:9 1 1 1 1 1 1 1 1 1
隔至少一個位置其實只要原本的if(mx-mm>=k)改成if(mx-mm>k)就行了。
最后大概描述一下不可重疊最長重復子串的解法:
- O(logn)二分枚舉子串長度,判斷解是否成立
- O(n)判斷長度是否成立:把互相之間LCP大於等於長度的分為一組,這通過個掃一遍height即可,因為后綴是有序的,相鄰的后綴間的LCP必定的極大的;接下來就找到每個組里后綴sa值最大和最小的,如果差值大於(等於)k就成立,因為這樣小下標的后綴沿着LCP下去走k步才不會蓋到大下標的后綴。
另外,說一下二分枚舉解,二分具體寫法很多吧,也不知道正不正確。。我那樣的寫法我發現:
- 如果是求最小解,mid要取floor,即mid=(left+right)/2
- 如果是求最大解,mid要取ceil,即mid=(left+right+1)/2
看起來好像是這個樣子的。。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define MAXN 22222 6 #define INF (1<<30) 7 int wa[MAXN],wb[MAXN],wv[MAXN],ws[MAXN]; 8 int cmp(int *r,int a,int b,int l){ 9 return r[a]==r[b] && r[a+l]==r[b+l]; 10 } 11 int sa[MAXN],rank[MAXN],height[MAXN]; 12 void SA(int *r,int n,int m){ 13 int *x=wa,*y=wb; 14 15 for(int i=0; i<m; ++i) ws[i]=0; 16 for(int i=0; i<n; ++i) ++ws[x[i]=r[i]]; 17 for(int i=1; i<m; ++i) ws[i]+=ws[i-1]; 18 for(int i=n-1; i>=0; --i) sa[--ws[x[i]]]=i; 19 20 int p=1; 21 for(int j=1; p<n; j<<=1,m=p){ 22 p=0; 23 for(int i=n-j; i<n; ++i) y[p++]=i; 24 for(int i=0; i<n; ++i) if(sa[i]>=j) y[p++]=sa[i]-j; 25 for(int i=0; i<n; ++i) wv[i]=x[y[i]]; 26 for(int i=0; i<m; ++i) ws[i]=0; 27 for(int i=0; i<n; ++i) ++ws[wv[i]]; 28 for(int i=1; i<m; ++i) ws[i]+=ws[i-1]; 29 for(int i=n-1; i>=0; --i) sa[--ws[wv[i]]]=y[i]; 30 swap(x,y); x[sa[0]]=0; p=1; 31 for(int i=1; i<n; ++i) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; 32 } 33 34 for(int i=1; i<n; ++i) rank[sa[i]]=i; 35 int k=0; 36 for(int i=0; i<n-1; height[rank[i++]]=k){ 37 if(k) --k; 38 for(int j=sa[rank[i]-1]; r[i+k]==r[j+k]; ++k); 39 } 40 } 41 42 int n,a[MAXN],r[MAXN]; 43 bool isok(int k){ 44 bool flag=0; 45 int mx=-INF,mm=INF; 46 for(int i=2; i<=n; ++i){ 47 if(height[i]>=k){ 48 mm=min(mm,min(sa[i],sa[i-1])); 49 mx=max(mx,max(sa[i],sa[i-1])); 50 if(mx-mm>k) return 1; 51 }else{ 52 mx=-INF,mm=INF; 53 } 54 } 55 return 0; 56 } 57 int main(){ 58 while(~scanf("%d",&n) && n){ 59 for(int i=0; i<n; ++i) scanf("%d",a+i); 60 --n; 61 for(int i=0; i<n; ++i) r[i]=a[i+1]-a[i]+88; 62 r[n]=0; 63 SA(r,n+1,176); 64 int l=0,r=n>>1; 65 while(l<r){ 66 int mid=l+r+1>>1; 67 if(isok(mid)) l=mid; 68 else r=mid-1; 69 } 70 if(l>=4) printf("%d\n",l+1); 71 else printf("%d\n",0); 72 } 73 return 0; 74 }
