【题目链接】
【题意】
求不可重叠最长重复子串。
2015-11-27
【思路】
1) 据题意处理字符串
2) 后缀数组。二分长度k,问题成为了判定是否存在两个及以上长度不小于k且互不重叠的子串。根据height数组划分后缀,满足两个条件:一是一组内height值不小于k(保证组内任两个长度不小于k即存在长度不小于k的子串),二是组内后缀sa值的最大最小值之差大于等于k(保证两个子串不重叠)。
需要注意n==1时需要特判。
1/为什么可以划分height数组呢?首先height[i]代表lcp(suffix(sa[i]),suffix(sa[i-1])),所以height所对应的后缀是有序的,如果划分出现height<k的话以后的后缀与改组内的lcp一定不大于k-1,所以不会出现后面的再划分到改组的情况。
【代码】
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #define FOR(a,b,c) for(int a=(b);a<=(c);a++) 5 using namespace std; 6 7 const int maxn = 40000+10; 8 9 int s[maxn]; 10 int sa[maxn],t[maxn],t2[maxn],c[maxn],n; 11 12 13 void build_sa(int m) { 14 int i,*x=t,*y=t2; 15 for(int i=0;i<m;i++) c[i]=0; 16 for(int i=0;i<n;i++) c[x[i]=s[i]]++; 17 for(int i=1;i<m;i++) c[i]+=c[i-1]; 18 for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; 19 20 for(int k=1;k<=n;k<<=1) { 21 int p=0; 22 for(int i=n-k;i<n;i++) y[p++]=i; 23 for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; 24 25 for(int i=0;i<m;i++) c[i]=0; 26 for(int i=0;i<n;i++) c[x[y[i]]]++; 27 for(int i=0;i<m;i++) c[i]+=c[i-1]; 28 for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; 29 30 swap(x,y); 31 p=1; x[sa[0]]=0; 32 for(int i=1;i<n;i++) 33 x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++; 34 if(p>=n) break; 35 m=p; 36 } 37 } 38 39 int rank[maxn],height[maxn]; 40 void getHeight() { 41 int i,j,k=0; 42 for(int i=0;i<n;i++) rank[sa[i]]=i; 43 for(int i=0;i<n;i++) { 44 if(k) k--; 45 int j=sa[rank[i]-1]; 46 while(s[i+k]==s[j+k]) k++; 47 height[rank[i]]=k; 48 } 49 } 50 51 bool can(int k) { 52 int min=sa[0],max=sa[0]; 53 for(int i=1;i<n;i++) { 54 if(height[i]<k) min=max=sa[i]; 55 if(sa[i]<min) min=sa[i]; 56 if(sa[i]>max) max=sa[i]; 57 if(max-min>=k) return true; 58 } 59 return false; 60 } 61 62 int main() { 63 while(scanf("%d",&n)==1 && n) 64 { 65 for(int i=0;i<n;i++)scanf("%d",&s[i]); 66 for(int i=n-1;i>0;i--)s[i]=s[i]-s[i-1]+100; 67 n--; 68 for(int i=0;i<n;i++)s[i]=s[i+1]; 69 s[n]=0; 70 build_sa(200); 71 getHeight(); 72 int L=0,R=n/2; 73 while(L<R) { 74 int M=L+(R-L+1)/2; 75 if(can(M)) L=M; else R=M-1; 76 } 77 L++; 78 if(L<=4) printf("0 "); 79 else printf("%d ",L); 80 } 81 return 0; 82 }
2016-2-19
【思路】
SAM+DP
处理出right集的最大值mx和最小值mn,即该状态对应所有字符串的结束位置的最大与最小,递推式为:
mx[p]=max{ l[p], mx[np],np->fa=p }
mn[p]=min{ l[p], mn[np],np->fa=p }
则状态i对应字符串中的最长重复子串的长度为min{l[i],mx[i]-mn[i]},可以拿个栗子自己看一下,这样保证了不重叠。然后取所有状态的最大值即可。
【代码】
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 using namespace std; 5 6 const int N = 4*1e4+10; 7 const int sigma = 180; 8 9 int s[N/2]; 10 int root,last,sz,ch[N][sigma],fa[N],l[N],mn[N],mx[N]; 11 int b[N],cnt[N],n; 12 13 void init() { 14 sz=0; root=last=++sz; 15 memset(fa,0,sizeof(fa)); 16 memset(mx,0,sizeof(mx)); 17 memset(mn,127,sizeof(mn)); 18 memset(cnt,0,sizeof(cnt)); 19 memset(ch,0,sizeof(ch)); 20 } 21 void add(int x) { 22 int c=s[x]; 23 int p=last,np=++sz; last=np; 24 mn[np]=mx[np]=l[np]=x; 25 for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; 26 if(!p) fa[np]=root; 27 else { 28 int q=ch[p][c]; 29 if(l[p]+1==l[q]) fa[np]=q; 30 else { 31 int nq=++sz; l[nq]=l[p]+1; 32 memcpy(ch[nq],ch[q],sizeof(ch[q])); 33 fa[nq]=fa[q]; 34 fa[np]=fa[q]=nq; 35 for(;p&&q==ch[p][c];p=fa[p]) ch[p][c]=nq; 36 } 37 } 38 } 39 void solve() { 40 for(int i=1;i<=sz;i++) cnt[l[i]]++; 41 for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1]; 42 for(int i=1;i<=sz;i++) b[cnt[l[i]]--]=i; 43 int ans=0; 44 for(int i=sz;i;i--) { 45 int p=b[i]; 46 if(fa[p]) { 47 if(mn[fa[p]]>mn[p]) mn[fa[p]]=mn[p]; 48 if(mx[fa[p]]<mx[p]) mx[fa[p]]=mx[p]; 49 } 50 } 51 for(int i=1;i<=sz;i++) 52 ans=max(ans,min(l[i],mx[i]-mn[i])); 53 if(ans<4) puts("0"); 54 else printf("%d ",ans+1); 55 } 56 void read(int& x) { 57 char c=getchar(); int f=1; x=0; 58 while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} 59 while(isdigit(c)) x=x*10+c-'0',c=getchar(); 60 x*=f; 61 } 62 63 int main() { 64 while(read(n),n) { 65 init(); 66 for(int i=1;i<=n;i++) read(s[i]); n--; 67 for(int i=1;i<=n;i++) s[i]=s[i+1]-s[i]+88,add(i); 68 solve(); 69 } 70 return 0; 71 }