题目大意:
给出长度为n的数字序列(n<20,000),求重复出次数不小于k的最长子序列(连续)的长度。例如序列 1 2 3 2 3 2 3,k=2,那么序列2 3 2 3重复出现了两次,长度为4。
分析:
此题可以用后缀数组的最长公共前缀LCP来解决。只是需要将字符数组换做数字数组来处理而已。
下面的讨论是基于已求出后缀数组并且做好LCP的预处理的前提下进行的。如何求后缀数组以及最长公共前缀请参考2004年许智磊的IOI国家队论文《后缀数组》。
论文中一个重要结论:LCP(i,j)=min{LCP(k-1,k)|i+1≤k≤j}。即,询问后缀数组中第i个后缀和第j个后缀的最长公共前缀,等同于询问i到j之间所有排名的相邻两个后缀的最长公共前缀。再换句话说,如果LCP(i,j)=lcp的话,说明i到j之间所有的串都和i,j有长度不小于lcp的公共前缀。
应用到此题上来,用两个间隔为k(至少重复的次数)的游标p,q,枚举所有长度为k的区间,询问LCP(p,q)=lcp,在这些lcp中找最大值即为所求。
复杂度后缀数组O(nlogn) + LCP预处理O(nlogn) + 枚举区间求LCP O(nlogn)。所以复杂度为O(nlogn)。
- /*
- PKU3261 Milk Patterns
- */
- #include <stdio.h>
- #include <memory.h>
- #include <stdlib.h>
- #define MAX(a,b) ((a)>(b)?(a):(b))
- #define MIN(a,b) ((a)>(b)?(b):(a))
- #define N 20005
- #define K 35
- int nday;
- /***************************************/
- int power;
- int lstrank[N];
- int cmpstr[N];
- int cmpChar(const void *a,const void *b){
- return cmpstr[*(int*)a] - cmpstr[*(int*)b];
- }
- int cmpLst(const void *A,const void *B){
- int a = *(int*)A;
- int b = *(int*)B;
- if(lstrank[a]==lstrank[b])
- return lstrank[a+power]-lstrank[b+power];
- return lstrank[a]-lstrank[b];
- }
- /*后缀数组
- 参数:
- 输入字符串str[];
- 返回后缀数组sa[];返回名次数组rank[]
- 说明:
- 复杂度O(nlogn)
- 需要stdlib.h辅助全局变量power, lstrank[],cmpstr[]
- */
- void suffixArray(int str[],int sa[],int rank[]){
- int i,j,n=nday;
- for(i=0;i<n;i++) sa[i]=i;
- memcpy(cmpstr,str,sizeof(cmpstr));
- qsort(sa,n,sizeof(int),cmpChar);
- for(i=j=0;i<n;i++){
- if(i>0&& str[sa[i]]!=str[sa[i-1]]) j++;
- rank[sa[i]]=j;
- }
- for(power=1;rank[sa[n-1]]<n-1; power*=2)
- {
- memcpy(lstrank,rank, sizeof(int)*n);
- qsort(sa,n,sizeof(int), cmpLst);
- for(i=j=0;i<n;i++){
- if(i>0&&cmpLst(&sa[i-1], &sa[i])) j++;
- rank[sa[i]]=j;
- }
- }
- }
- /*区间最大值询问
- preRMQ()用O(nlogn)时间预处理
- RMQ()用O(1)的时间询问i~j之间的最大值
- */
- int dr[N][K]={0};
- void preRMQ(int a[],int n){
- int i,j,k,f,s;
- for(i=0;i<n;i++) dr[i][0]=a[i];
- for(s=f=1;s<n;f++){
- for(i=0;;i++){
- if(i+s>=n) break;
- dr[i][f]= MIN(dr[i][f-1],dr[i+s][f-1]);
- }
- s=1<<f;
- }
- }
- int RMQ(int p,int q){
- int d,f;
- d=q-p+1;
- for(f=0;(1<<f)<=d;f++); f--;
- return MIN(dr[p][f],dr[q-(1<<f)+1][f]);
- }
- /*预处理LCP求h[],height[]
- 参数:
- 输入原字符串str[]
- 输入后缀数组sa[]
- 输入名次数组rank[]
- 返回height[]
- 说明:
- 复杂度O(n)
- 需要事先用suffixArray计算sa[]和rank[]
- height[i]=LCP(i-1,i)
- h[i]=height[rank[i]]
- height[i]=h[sa[i]]
- */
- void preLCP(int str[],int sa[],int rank[],int height[]){
- int i,j,k,s,n=nday;
- int h[N];
- for(i=0;i<n;i++){
- if(rank[i]==0){
- h[i]=0; continue;
- }
- j=rank[i]-1;
- k=rank[i];
- if(i==0||h[i-1]<=1) s=0;
- else s=h[i-1]-1;
- for(;sa[k]+s<n && sa[j]+s<n;s++)
- if(str[sa[k]+s]!= str[sa[j]+s]) break;
- h[i]=s;
- }
- for(i=0;i<n;i++) height[rank[i]]= h[i];
- preRMQ(height,n);
- }
- /*
- LCP询问后缀数组sa[]中x和y的最长公共前缀,复杂度O(logn)
- */
- int LCP(int x,int y){
- if(x<y) return RMQ(x+1,y);
- else return RMQ(y+1,x);
- }
- /**************************************/
- int n,m;
- int str[N];
- int sa[N];
- int rank[N];
- int height[N];
- void printa(int a[]){
- int i;
- for(i=0;a[i];i++) printf("%d",a[i]-1);
- puts("");
- }
- int main()
- {
- int i,j,k;
- int ans;
- while(scanf("%d%d",&n,&m)!=EOF){
- nday=n+1;
- for(i=0;i<n;i++){
- scanf("%d",&str[i]); str[i]++;
- }
- str[n]=0;
- suffixArray(str,sa,rank);
- preLCP(str,sa,rank,height);
- /*
- for(i=0;i<n;i++){
- printf("%d - %d: ",i,sa[i]);
- printa(str+sa[i]);
- }*/
- ans=0;
- for(i=0,j=m-1;j<=n;i++,j++){
- k=LCP(i,j);
- ans=MAX(ans,k);
- }
- printf("%d/n",ans);
- }
- return 0;
- }