PKU 3261 Milk Pattern - 后缀数组+LCP

题目大意:

给出长度为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)。

 

  1. /*
  2. PKU3261 Milk Patterns
  3. */
  4. #include <stdio.h>
  5. #include <memory.h>
  6. #include <stdlib.h>
  7. #define MAX(a,b) ((a)>(b)?(a):(b))
  8. #define MIN(a,b) ((a)>(b)?(b):(a))
  9. #define N 20005
  10. #define K 35
  11. int nday;
  12. /***************************************/
  13. int power;
  14. int lstrank[N];
  15. int cmpstr[N];
  16. int cmpChar(const void *a,const void *b){
  17.     return cmpstr[*(int*)a] - cmpstr[*(int*)b];
  18. }
  19. int cmpLst(const void *A,const void *B){
  20.     int a = *(int*)A;
  21.     int b = *(int*)B;
  22.     if(lstrank[a]==lstrank[b]) 
  23.         return lstrank[a+power]-lstrank[b+power];
  24.     return lstrank[a]-lstrank[b];
  25. }
  26. /*后缀数组
  27. 参数:
  28.     输入字符串str[];
  29.     返回后缀数组sa[];返回名次数组rank[]
  30. 说明:
  31.     复杂度O(nlogn)
  32.     需要stdlib.h辅助全局变量power, lstrank[],cmpstr[]
  33. */
  34. void suffixArray(int str[],int sa[],int rank[]){
  35.     int i,j,n=nday;
  36.     for(i=0;i<n;i++) sa[i]=i;
  37.     memcpy(cmpstr,str,sizeof(cmpstr));
  38.     qsort(sa,n,sizeof(int),cmpChar);
  39.     for(i=j=0;i<n;i++){
  40.         if(i>0&& str[sa[i]]!=str[sa[i-1]]) j++;
  41.         rank[sa[i]]=j;
  42.     }
  43.     for(power=1;rank[sa[n-1]]<n-1; power*=2)
  44.     {
  45.         memcpy(lstrank,rank, sizeof(int)*n);
  46.         qsort(sa,n,sizeof(int), cmpLst);
  47.         for(i=j=0;i<n;i++){
  48.             if(i>0&&cmpLst(&sa[i-1], &sa[i])) j++;
  49.             rank[sa[i]]=j;
  50.         }
  51.     }
  52. }
  53. /*区间最大值询问
  54.     preRMQ()用O(nlogn)时间预处理
  55.     RMQ()用O(1)的时间询问i~j之间的最大值 
  56. */
  57. int dr[N][K]={0};
  58. void preRMQ(int a[],int n){
  59.     int i,j,k,f,s;
  60.     for(i=0;i<n;i++) dr[i][0]=a[i];
  61.     for(s=f=1;s<n;f++){
  62.         for(i=0;;i++){
  63.             if(i+s>=n) break;
  64.             dr[i][f]= MIN(dr[i][f-1],dr[i+s][f-1]);
  65.         }
  66.         s=1<<f;
  67.     }
  68. }
  69. int RMQ(int p,int q){
  70.     int d,f;
  71.     d=q-p+1;
  72.     for(f=0;(1<<f)<=d;f++); f--;
  73.     return MIN(dr[p][f],dr[q-(1<<f)+1][f]);
  74. }
  75. /*预处理LCP求h[],height[] 
  76. 参数:
  77.     输入原字符串str[] 
  78.     输入后缀数组sa[]
  79.     输入名次数组rank[]
  80.     返回height[]
  81. 说明:
  82.     复杂度O(n)
  83.     需要事先用suffixArray计算sa[]和rank[] 
  84.     height[i]=LCP(i-1,i)
  85.     h[i]=height[rank[i]]
  86.     height[i]=h[sa[i]]
  87. */
  88. void preLCP(int str[],int sa[],int rank[],int height[]){
  89.     int i,j,k,s,n=nday;
  90.     int h[N];
  91.     
  92.     for(i=0;i<n;i++){
  93.         if(rank[i]==0){
  94.             h[i]=0; continue;
  95.         }
  96.         j=rank[i]-1;
  97.         k=rank[i];
  98.         if(i==0||h[i-1]<=1) s=0;
  99.         else s=h[i-1]-1;
  100.         for(;sa[k]+s<n && sa[j]+s<n;s++)
  101.             if(str[sa[k]+s]!= str[sa[j]+s]) break;
  102.         h[i]=s;
  103.     }
  104.     for(i=0;i<n;i++) height[rank[i]]= h[i];
  105.     preRMQ(height,n);
  106. }
  107. /*
  108. LCP询问后缀数组sa[]中x和y的最长公共前缀,复杂度O(logn) 
  109. */
  110. int LCP(int x,int y){
  111.     if(x<y) return RMQ(x+1,y);
  112.     else return RMQ(y+1,x);
  113. }
  114. /**************************************/
  115. int n,m;
  116. int str[N];
  117. int sa[N];
  118. int rank[N];
  119. int height[N];
  120. void printa(int a[]){
  121.     int i;
  122.     for(i=0;a[i];i++) printf("%d",a[i]-1);
  123.     puts("");
  124. }
  125. int main()
  126. {
  127.     int i,j,k;
  128.     int ans;
  129.     
  130.     while(scanf("%d%d",&n,&m)!=EOF){
  131.         nday=n+1;
  132.         for(i=0;i<n;i++){
  133.             scanf("%d",&str[i]); str[i]++;
  134.         }
  135.         str[n]=0;
  136.         suffixArray(str,sa,rank);
  137.         preLCP(str,sa,rank,height);
  138.         /*
  139.         for(i=0;i<n;i++){
  140.             printf("%d - %d: ",i,sa[i]);
  141.             printa(str+sa[i]);
  142.         }*/
  143.         ans=0;
  144.         for(i=0,j=m-1;j<=n;i++,j++){
  145.             k=LCP(i,j);
  146.             ans=MAX(ans,k);
  147.         }
  148.         printf("%d/n",ans);
  149.     }
  150.     return 0;
  151. }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值