poj 3261 Milk Patterns(后缀数组)(k次的最长重复子串)

Milk Patterns
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 7938 Accepted: 3598
Case Time Limit: 2000MS

Description

Farmer John has noticed that the quality of milk given by his cows varies from day to day. On further investigation, he discovered that although he can't predict the quality of milk from one day to the next, there are some regular patterns in the daily milk quality.

To perform a rigorous study, he has invented a complex classification scheme by which each milk sample is recorded as an integer between 0 and 1,000,000 inclusive, and has recorded data from a single cow overN (1 ≤ N ≤ 20,000) days. He wishes to find the longest pattern of samples which repeats identically at least K (2 ≤ K  N) times. This may include overlapping patterns -- 1 2 3 2 3 2 3 1 repeats 2 3 2 3 twice, for example.

Help Farmer John by finding the longest repeating subsequence in the sequence of samples. It is guaranteed that at least one subsequence is repeated at least K times.

Input

Line 1: Two space-separated integers:   N  and   K   Lines 2.. N+1:   N  integers, one per line, the quality of the milk on day   i  appears on the   ith line.

Output

Line 1: One integer, the length of the longest pattern which occurs at least   K  times

Sample Input

8 2
1
2
3
2
3
2
3
1

Sample Output

4

给定一个字符串,求至少出现k次的最长重复子串,这k个子串可以重叠。

算法分析:这题的做法和上一题差不多,也是先二分答案,然后将后缀分成若干组。不同的是,这里要判断的是有没有一个组的后缀个数不小于k。如果有,那么存在k个相同的子串满足条件,否则不存在。这个做法的时间复杂度为 O(nlogn)。

下面给一组数据:5 2  1 1 1 1 1    一组数据发现RE bug

先贴个RE代码:

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include<string.h>
  4 
  5 using namespace std;
  6 
  7 #define maxn 1100000
  8 
  9 #define cls(x) memset(x, 0, sizeof(x))
 10 
 11 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
 12 
 13 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
 14 
 15 //倍增算法
 16 void da(int *r,int *sa,int n,int m)
 17 {
 18      cls(wa);
 19      cls(wb);
 20      cls(wv);
 21      int i,j,p,*x=wa,*y=wb,*t;
 22 
 23      //基数排序
 24      for(i=0;i<m;i++) wss[i]=0;
 25      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
 26      for(i=1;i<m;i++) wss[i]+=wss[i-1];
 27      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
 28 
 29      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
 30      for(j=1,p=1;p<n;j*=2,m=p)
 31      {
 32        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
 33        for(p=0,i=n-j;i<n;i++) y[p++]=i;
 34        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
 35 
 36        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
 37        for(i=0;i<n;i++) wv[i]=x[y[i]];
 38        for(i=0;i<m;i++) wss[i]=0;
 39        for(i=0;i<n;i++) wss[wv[i]]++;
 40        for(i=1;i<m;i++) wss[i]+=wss[i-1];
 41        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
 42 
 43        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
 44        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
 45        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
 46      }
 47 }
 48 
 49 int rank[maxn],height[maxn];
 50 
 51 //得到height数组:排名相邻的两个后缀的最长公共前缀
 52 void calheight(int *r,int *sa,int n)
 53 {
 54      cls(rank);
 55      cls(height);
 56      int i,j,k=0;
 57      for(i=1;i<n;i++) rank[sa[i]]=i;
 58      for(i=0;i<n;height[rank[i++]]=k)
 59      for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
 60      return;
 61 }
 62     
 63 int ca[maxn];
 64 int sa[maxn];
 65 int N;
 66 
 67 int isok(int m,int k){
 68     int sum=0,i;
 69     for(i=1;i<N;i++){
 70         if(height[i]>=m){
 71             sum++;
 72             if(sum>=k-1){
 73                 return 1;
 74             }
 75         }else{
 76             sum=0;
 77         }
 78     }
 79     return 0;
 80 }
 81 
 82 int main()
 83 {
 84     int k,i;
 85     while (~scanf("%d%d",&N,&k))
 86     {
 87         for(i=0;i<N;i++)
 88             scanf("%d",&ca[i]);
 89         da(ca, sa, N, maxn);
 90         calheight(ca,sa,N);
 91 
 92      //  for(i=0;i<N;i++)  printf("%d ",height[i]); putchar(10);
 93         int j;
 94         i=0;
 95         j=N;
 96         while(i<=j){
 97             int mid = (i+j)/2;
 98             if(isok(mid,k)){
 99                 i=mid+1;
100             }else{
101                 j=mid-1;
102             }
103         }
104         printf("%d\n",j);
105     }
106     return 0;
107 }
View Code

再来一个AC码:(根据RE代码改过来的)

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include<string.h>
  4 
  5 using namespace std;
  6 
  7 #define maxn 1100000
  8 
  9 #define cls(x) memset(x, 0, sizeof(x))
 10 
 11 int wa[maxn],wb[maxn],wv[maxn],wss[maxn];
 12 
 13 int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
 14 
 15 //倍增算法
 16 void da(int *r,int *sa,int n,int m)
 17 {
 18      cls(wa);
 19      cls(wb);
 20      cls(wv);
 21      int i,j,p,*x=wa,*y=wb,*t;
 22 
 23      //基数排序
 24      for(i=0;i<m;i++) wss[i]=0;
 25      for(i=0;i<n;i++) wss[x[i]=r[i]]++;
 26      for(i=1;i<m;i++) wss[i]+=wss[i-1];
 27      for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
 28 
 29      // 在第一次排序以后,rank数组中的最大值小于p,所以让m=p。整个倍增算法基本写好,代码大约25行。
 30      for(j=1,p=1;p<n;j*=2,m=p)
 31      {
 32        //接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关键字排序的结果实际上可以利用上一次求得的sa直接算出,没有必要再算一次
 33        for(p=0,i=n-j;i<n;i++) y[p++]=i;
 34        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
 35 
 36        //其中变量j是当前字符串的长度,数组y保存的是对第二关键字排序的结果。然后要对第一关键字进行排序,
 37        for(i=0;i<n;i++) wv[i]=x[y[i]];
 38        for(i=0;i<m;i++) wss[i]=0;
 39        for(i=0;i<n;i++) wss[wv[i]]++;
 40        for(i=1;i<m;i++) wss[i]+=wss[i-1];
 41        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
 42 
 43        //这样便求出了新的sa值。在求出sa后,下一步是计算rank值。
 44        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
 45        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
 46      }
 47 }
 48 
 49 int rank[maxn],height[maxn];
 50 
 51 //得到height数组:排名相邻的两个后缀的最长公共前缀
 52 void calheight(int *r,int *sa,int n)
 53 {
 54      cls(rank);
 55      cls(height);
 56      int i,j,k=0;
 57      
 58      for(i=1;i<n;i++) rank[sa[i]]=i;
 59     // for(i=0;i<n;i++)  printf("%d ",rank[i]); putchar(10); system("pause");
 60 
 61      for(i=0;i<n;height[rank[i++]]=k)
 62          for(k?k--:0,j=sa[((rank[i]-1<0)?0:rank[i]-1)];r[i+k]==r[j+k]&&i!=j;k++);   // 加上r[i+k]==r[j+k] => r[i+k]==r[j+k]&&i!=j
 63      return;
 64 }
 65     
 66 int ca[maxn];
 67 int sa[maxn];
 68 int N;
 69 
 70 int isok(int m,int k){
 71     int sum=0,i;
 72     for(i=1;i<N;i++){
 73         if(height[i]>=m){
 74             sum++;
 75             if(sum>=k-1){
 76                 return 1;
 77             }
 78         }else{
 79             sum=0;
 80         }
 81     }
 82     return 0;
 83 }
 84 
 85 int main()
 86 {
 87     int k,i;
 88     while (~scanf("%d%d",&N,&k))
 89     {
 90         for(i=0;i<N;i++)
 91             scanf("%d",&ca[i]);
 92         ca[N] = 1000000;   //加上一个结尾,排除数字全相同的情况
 93         N++;
 94         da(ca, sa, N, maxn-1);
 95         calheight(ca,sa,N);
 96 
 97         int j;
 98         i=0;
 99         j=N;
100         while(i<=j){
101             int mid = (i+j)/2;
102             if(isok(mid,k)){
103                 i=mid+1;
104             }else{
105                 j=mid-1;
106             }
107         }
108         printf("%d\n",j);
109     }
110     return 0;
111 }


后缀数组第二种实现方式:AC码:

  1 #include<iostream>
  2 #include<stdio.h>
  3 #include<string.h>
  4 #define MAXN 500010
  5 
  6 char str[MAXN*2];
  7 int s[MAXN*2],sa[MAXN*2],rank[MAXN*2],trank[MAXN*4],sum[MAXN*4],tsa[MAXN*2],height[MAXN*2];
  8 
  9 void sorting(int j,int len)//基数排序
 10 {
 11     int i;
 12     memset(sum,0,sizeof(sum));
 13     for (i=1; i<=len; i++) sum[ rank[i+j] ]++;
 14     for (i=1; i<=len; i++) sum[i]+=sum[i-1];
 15     for (i=len; i>0; i--) tsa[ sum[ rank[i+j] ]-- ]=i;//对第二关键字计数排序,tsa代替sa为排名为i的后缀是tsa[i]
 16 
 17     memset(sum,0,sizeof(sum));
 18     for (i=1; i<=len; i++) sum[ rank[i] ]++;
 19     for (i=1; i<=len; i++) sum[i]+=sum[i-1];
 20     for (i=len; i>0; i--) sa[ sum[ rank[ tsa[i] ] ]-- ]= tsa[i]; //对第一关键字计数排序
 21     //构造互逆关系
 22 //    for(i=1;i<=len;i++) printf("%d ",rank[i]); putchar(10);
 23 }
 24 
 25 void getsa(int len){
 26 
 27      memset(sum,0,sizeof(sum));
 28      memset(rank,0,sizeof(rank));
 29      memset(height,0,sizeof(height));
 30      memset(trank,0,sizeof(trank));
 31      memset(sa,0,sizeof(sa));
 32     memset(tsa,0,sizeof(tsa));
 33 
 34     int i;
 35     for (i=0; i<len; i++) {
 36         trank[i+1]=s[i];
 37     }
 38     for (i=1; i<=len; i++) {
 39         sum[ trank[i] ]++;
 40     }
 41     for (i=1; i<=40; i++) sum[i]+=sum[i-1];
 42     for (i=len; i>0; i--) sa[ sum[ trank[i] ]-- ]=i;
 43 
 44     rank[ sa[1] ]=1;
 45 
 46     int p;
 47     for (i=2,p=1; i<=len; i++)
 48     {
 49         if (trank[ sa[i] ]!=trank[ sa[i-1] ]) p++;
 50         rank[ sa[i] ]=p;
 51     }//第一次的sa与rank构造完成
 52 
 53     for (int j=1; j<=len; j*=2)
 54     {
 55         sorting(j,len);
 56         trank[ sa[1] ]=1; 
 57         p=1; //用trank代替rank
 58         for (i=2; i<=len; i++)
 59         {
 60             if ((rank[ sa[i] ]!=rank[ sa[i-1] ]) || (rank[ sa[i]+j ]!=rank[ sa[i-1]+j ])) p++;
 61             trank[ sa[i] ]=p;//空间要开大一点,至少2倍
 62         }
 63         for (i=1; i<=len; i++) rank[i]=trank[i];
 64     }
 65 }
 66 
 67 void getheight(int len)
 68 {
 69     for (int i=1,j=0; i<=len; i++)//用j代替上面的h数组
 70     {
 71         if (rank[i]==1) continue;
 72         for (; s[i+j-1]==s[ sa[ rank[i]-1 ]+j-1 ]; ) j++;//注意越界之类的问题
 73         height[ rank[i] ]=j;
 74         if (j>0) j--;
 75     }
 76 }
 77 int N;
 78 
 79 int isok(int m,int k){
 80     int sum=0,i;
 81     for(i=2;i<=N;i++){
 82         if(height[i]>=m){
 83             sum++;
 84             if(sum>=k-1){
 85                 return 1;
 86             }
 87         }else{
 88             sum=0;
 89         }
 90     }
 91     return 0;
 92 }
 93 
 94 int main()
 95 {
 96     int k,i;
 97     while (~scanf("%d%d",&N,&k))
 98     {
 99         for(i=0;i<N;i++)
100             scanf("%d",&s[i]);
101         getsa(N);
102         getheight(N);
103 
104         int j;
105         i=0;
106         j=N;
107         while(i<=j){
108             int mid = (i+j)/2;
109             if(isok(mid,k)){
110                 i=mid+1;
111             }else{
112                 j=mid-1;
113             }
114         }
115         printf("%d\n",j);
116     }
117     return 0;
118 }

 

 

转载于:https://www.cnblogs.com/crazyapple/p/3200037.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值