1. 程式人生 > >POJ 3693 Maximum repetition substring 字尾陣列與區間最值的完美結合

POJ 3693 Maximum repetition substring 字尾陣列與區間最值的完美結合

題目要求完美求出重複次數最多的連續重複子串,首先我們不知道如果重複次數最多的子串的長度是多少,其次我們只有知道了長度才可以知道重複的次數和起始位置……可見首先要確定的是長度!
那麼我們可以先從1開始列舉長度L,從1列舉到len/2 len代表字串總長度。那麼我們可以知道假設字串為st 那麼 st[0] st[L] st[2*L] ….st[kL] 中肯定有兩個連續的出現在字串中,那麼我們開列舉兩個連續的之後從這兩個字元前後開始匹配,看最遠匹配多遠

即以st[i*L],st[i*L+L]前後匹配,這裡是通過查詢suffix(i*L),suffix(i*L+L)的最長公共字首
通過rank值能找到 i*L,與 i*L+L 的排名,我們要查詢的是這段區間的height的最小值,通過RMQ預處理
(這裡是為了這段排名區間內的最大公共子串即height最小值),我們可以通過 st演算法預處理一次達到查詢為0(1)的複雜度,設LCP長度為M, 則答案顯然為M / L + 1(即出現了M/L+1次), 但這只是以i*L 和i*L+L為起點的情況, 不過有一點是可以確定的。
如果目標子串包含了 i*L和i*L+L。那麼 i*L一定是和i*L+L匹配的。因為目標串中p一定和p+L匹配。這樣才能滿足子串長度為L。先在要解決的就是起點不在這兩個位置上怎麼辦了。
得到M/L+1我們可以試著把答案變大。如果M%L!=0我們可以把長度補齊到L的整數倍。即在前面增加(L-M%L)的字元.看能不能使答案變大。為什麼這樣做是可以的呢?因為我們要使啊、答案變大往後擴充套件肯定不行了。因為後面已經不匹配了。但是我們為什麼擴充套件 (L-M%L)這麼多就行了呢。比這個小肯定是不行的。因為還是沒到L的整數倍。比這個多能行的話。去這個值一定能行。因為p是和p+L匹配的。既然取得比這個多。大不了往右平移幾個還是能使 M%L得到匹配。那為什麼只擴充套件一個長度L。不擴充套件多個呢。因為你是列舉每個i*L 和i*L+l。你擴充套件2個或兩個以上就是前面的 i*L和i*L+l的情況了。這一步完成後我們只能得到度數最大長度可能的取值。剩下的工作就是找字典序最小了。 通過sa陣列進行列舉,取到的第一組,肯定是字典序最小的。

程式碼如下:

#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <cmath>
#include <iostream>
#define maxs 2000010
#define MME(i,j) memset(i,j,sizeof(i))
using namespace std;
int dp[maxs][33];
int wa[maxs],wb[maxs],wv[maxs],sa[maxs],wd[maxs];
int ranks[maxs],height[maxs],s[maxs];
char
str[maxs]; bool cmp(int *r,int a,int b,int k) { return r[a]==r[b]&&r[a+k]==r[b+k]; } void getsa(int *r,int n,int m) { int i,j,p,*x=wa,*y=wb; for(i=0;i<m;i++) wd[i]=0; for(i=0;i<n;i++) wd[x[i]=r[i]]++; for(i=1;i<m;i++) wd[i]+=wd[i-1]; for(i=n-1;i>=0;i--) sa[--wd[x[i]]]=i; for
(j=1,p=1;p<n;j*=2,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) wd[i]=0; for(i=0;i<n;i++) wd[wv[i]]++; for(i=1;i<m;i++) wd[i]+=wd[i-1]; for(i=n-1;i>=0;i--) sa[--wd[wv[i]]]=y[i]; for(swap(x,y),x[sa[0]]=0,p=1,i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } } void getheight(int *r,int n) { int i,j,k=0; for(i=1;i<=n;i++) ranks[sa[i]]=i; for(i=0;i<n;i++){ if(k) k--; else k=0; j=sa[ranks[i]-1]; while(r[i+k]==r[j+k]) k++; height[ranks[i]]=k; } } //以上求 sa ,height void rmq_init(int *a,int n){ for(int i=1;i<=n;i++) dp[i][0]=a[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]); } //ST 預處理 int rmq_ask(int ll,int rr) { int k; ll=ranks[ll]; rr=ranks[rr]; //注意這裡我們要找到這兩的排名,之後在這段排名中找到最小的height值 if(ll>rr) swap(ll,rr); ll++; k=(int)(log(rr-ll+1.0)/log(2.0)); return min(dp[ll][k],dp[rr-(1<<k)+1][k]); } //查詢 int main() { static int times=1; while(~scanf("%s",str)&&str[0]!='#') { int len=strlen(str); for(int i=0;i<len;i++) s[i]=str[i]-'a'+1; s[len]=0; getsa(s,len+1,30); getheight(s,len); rmq_init(height,len); int ans=-1,anspos,anslen; for(int L=1;L<=len/2;L++) // 列舉長度 { for(int j=0;j<len-L;j+=L) { if(str[j]!=str[j+L])//這裡一定要相等啊 continue; int k=rmq_ask(j,j+L);//區間最小值 int tol=k/L+1;//假定出現次數 int temppos=j;//假設的初始位置 int cnt=0,yu=L-k%L;//這裡看有多少個能再補一個l的長度 for(int m=j-1;m>j-L&&str[m]==str[m+L]&&m>=0;m--)//從這段區間開始往前找 { cnt++; if(cnt==yu)//如果 { tol++; temppos=m; } else if(ranks[m]<ranks[temppos])//保證了字典序 temppos=m; } if(ans<tol) { ans=tol; anspos=temppos; anslen=tol*L; } else if(ans==tol&&ranks[temppos]<ranks[anspos]) { anspos=temppos; anslen=tol*L; } } } printf("Case %d: ",times++); if(ans==1) { char c='z'; for(int i=0;i<len;i++) if(str[i]<c) c=str[i]; printf("%c\n",c); } else { for(int i=anspos;i<anspos+anslen;i++) { printf("%c",str[i]); } puts(""); } } return 0; }