SPOJ 687 Repeats (后缀数组+RMQ 重复次数最多的连续重复子串)

来源:互联网 发布:unity3d 添加模型 编辑:程序博客网 时间:2024/05/16 11:15

参考了 罗穗骞 关于后缀数组的论文,和 SPOJ 687 Repeats - Staginner - 博客园

题意:给定一个字符串,求重复次数最多的连续重复子串。

  算法分析:

  先穷举长度L,然后求长度为L的子串最多能连续出现几次。首先连续出现1次是肯定可以的,所以这里只考虑至少2次的情况。假设在原字符串中连续出现2次,记这个子字符串为S,那么S肯定包括了字符r[0],r[L],r[L*2],r[L*3],……中的某相邻的两个。所以只须看字符r[L*i]和r[L*(i+1)]往前和往后各能匹配到多远,记这个总长度为K,那么这里连续出现了K/L+1次。最后看最大值是多少。如图7所示。

  穷举长度L的时间是n,每次计算的时间是n/L。所以整个做法的时间复杂度是O(n/1+n/2+n/3+……+n/n)=O(nlogn)。

论文里说过这样一句话“所以只需看字符r[L*i]和r[L*(i+1)]往前和往后能匹配到多远”,对于往后能匹配到多远,这个直接根据最长公共前缀就能很容易得到,而对于往前能匹配到多远,我们当然可以一开始就把字符串反过来拼在后面,这样也能根据最长公共前缀来看往前能匹配到多远,但这样效率就比较低了。

    其实,当枚举到合适的子串长度时,我们在枚举r[L*i]和r[L*(i+1)]的过程中,必然可以出现r[L*i]在第一个循环节里,而r[L*(i+1)]在第二个循环节里的这种情况,如果此时r[L*i]是第一个循环节的首字符,这样直接用公共前缀k除以i并向下取整就可以得到最后结果。但如果r[L*i]如果不是首字符,这样算完之后结果就有可能偏小,因为r[L*i]前面可能还有少许字符也能看作是第一个循环节里的。

    于是,我们不妨先算一下,从r[L*i]开始,除匹配了k/i个循环节,还剩余了几个字符,剩余的自然是k%i个字符。如果说r[L*i]的前面还有i-k%i个字符完成比配的话,这样就相当于还可以再匹配出一个循环节,于是我们只要检查一下从r[L*i-(i-k%i)]和r[L*i-(i-k%i)+i]开始是否有i-k%i个字符能够完成匹配即可,也就是说去检查这两个后缀的最长公共前缀是否比i-k%i大即可。

其实由后缀数组的性质,如果公共前缀大于等于i-k%i,自然就大于等于i,因为后面的字符都是可以匹配上的,所以程序里面就直接去看是否会比i小也可以。

#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#define max(a,b) ((a)>(b)?(a):(b))#define min(a,b) ((a)<(b)?(a):(b))const int N = int(5e4)+10;#define F(x) ((x)/3+((x)%3==1?0:tb))#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)int wa[N],wb[N],wv[N],ws[N];int c0 (int *r,int a,int b){return r[a]==r[b] && r[a+1]==r[b+1] && r[a+2]==r[b+2];}int c12 (int k,int *r,int a,int b){if (k==2) return r[a]<r[b] || r[a]==r[b] && c12(1,r,a+1,b+1);else return r[a]<r[b] || r[a]==r[b] && wv[a+1]<wv[b+1];}void sort (int *r,int *a,int *b,int n,int m){int i;for(i=0;i<n;i++) wv[i]=r[a[i]];for(i=0;i<m;i++) ws[i]=0;for(i=0;i<n;i++) ws[wv[i]]++;for(i=1;i<m;i++) ws[i]+=ws[i-1];for(i=n-1;i>=0;i--) b[--ws[wv[i]]]=a[i];}void DC3 (int *r,int *sa,int n,int m){int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;r[n]=r[n+1]=0;for(i=0;i<n;i++) if(i%3!=0) wa[tbc++]=i;sort(r+2,wa,wb,tbc,m);sort(r+1,wb,wa,tbc,m);sort(r,wa,wb,tbc,m);for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;if(p<tbc) DC3(rn,san,tbc,p);else for(i=0;i<tbc;i++) san[rn[i]]=i;for(i=0;i<tbc;i++) if(san[i]<tb) wb[ta++]=san[i]*3;if(n%3==1) wb[ta++]=n-1;sort(r,wb,wa,ta,m);for(i=0;i<tbc;i++) wv[wb[i]=G(san[i])]=i;for(i=0,j=0,p=0;i<ta && j<tbc;p++)sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];for(;i<ta;p++) sa[p]=wa[i++];for(;j<tbc;p++) sa[p]=wb[j++];}  int rank[N],height[N],sa[3*N],data[3*N];void calheight(int *r,int *sa,int n){//memset(height,0,sizeof(height));//memset(rank,0,sizeof(rank));int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i<n; height[rank[i++]] = k )for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++);}char str[N];int Log[N],best[20][N];void initRMQ (int n){//初始化RMQint i;Log[0] = -1;for (i=1;i<=n;i++)        Log[i]=(i&(i-1))?Log[i-1]:Log[i-1]+1;for (i=1;i<=n;i++) best[0][i]=height[i];for (i=1;i<=Log[n];i++){int limit=n-(1<<i)+1;for (int j=1;j<=limit;j++)best[i][j] = min(best[i-1][j] , best[i-1][j+(1<<i>>1)]);}}int lcp (int a,int b){//询问a,b后缀的最长公共前缀a=rank[a]; b=rank[b];if (a>b) std::swap(a,b);a++;int t=Log[b-a+1];return min(best[t][a] , best[t][b - (1<<t) + 1]);}int main (){  int T,n;scanf("%d",&T);while (T--){scanf("%d\n",&n);char ch;int i,j;for (i=0;i<n;i++){scanf("%c\n",&ch);data[i]=ch;}data[n]=0;DC3(data,sa,n+1,128);calheight(data,sa,n);initRMQ(n);int ans=0;for (i=1;i<n;i++)  //枚举长度for (j=0;j+i<n;j+=i) //+i极大的降低了复杂度{//但也产生了需要向前比较的问题int k = lcp(j,j+i);   //公共后缀的长度int cnt = k/i+1;      //出现次数int t = j- (i - k%i); //前推到t位置if (t>=0 && lcp(t,t+i)>=(i-k%i))cnt++;ans=max(ans,cnt);}printf("%d\n",ans);  }return 0;  } 


0 0
原创粉丝点击