大致题意:
给出一个数字m和一个字符串str,求出str中至少出现m次的最长的子串长度,并且求出这些子串中最靠右的子串的位置。
大致思路:
先求出后缀数组,二分枚举这个长度mid,然后分析height数组的每个大于mid的区间。过程稍复杂,具体看我代码即可。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int inf=1<<30; const int nMax=500000; int num[nMax]; int sa[nMax], rank[nMax], height[nMax]; int wa[nMax], wb[nMax], wv[nMax], wd[nMax]; int cmp(int *r, int a, int b, int l){ return r[a] == r[b] && r[a+l] == r[b+l]; } void da(int *r, int n, int m){ // 倍增算法 r为待匹配数组 n为总长度 m为字符范围 int i, j, p, *x = wa, *y = wb, *t; for(i = 0; i < m; i ++) wd[i] = 0; for(i = 0; i < n; i ++) wd[x[i]=r[i]] ++; for(i = 1; i < m; i ++) wd[i] += wd[i-1]; for(i = n-1; i >= 0; i --) sa[-- wd[x[i]]] = i; for(j = 1, p = 1; p < n; j *= 2, m = p){ for(p = 0, i = n-j; i < n; i ++) y[p ++] = i; for(i = 0; i < n; i ++) if(sa[i] >= j) y[p ++] = sa[i] - j; for(i = 0; i < n; i ++) wv[i] = x[y[i]]; for(i = 0; i < m; i ++) wd[i] = 0; for(i = 0; i < n; i ++) wd[wv[i]] ++; for(i = 1; i < m; i ++) wd[i] += wd[i-1]; for(i = n-1; i >= 0; i --) sa[-- wd[wv[i]]] = y[i]; for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i ++){ x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1: p ++; } } } void calHeight(int *r, int n){ // 求height数组。 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 ++); } } int loc[nMax],m,ans; char str[nMax],res[nMax]; int abs(int a){ if(a>0)return a; return -a; } bool check(int mid,int len){ int i,j,a=0,b=0,c=0,flag=0; for(i=2;i<=len;i++){ if(height[i]>=mid){ a++; b=max(sa[i],max(sa[i-1],b)); } else{ if(a>=m-1){ c=max(b,c); flag=1; } b=0; a=0; } } if(a>=m-1){ c=max(b,c); flag=1; } if(flag)ans=c; return flag; } int main(){ int n,i,sp,ddd; while(scanf("%d",&m)!=EOF&&m){ scanf("%s",str); n=strlen(str); sp=30; if(m==1){ printf("%d %d\n",n,0); continue; } for(i=0;str[i];i++){ num[i]=str[i]-'a'+1; } num[n]=0; da(num,n+1,sp+4); calHeight(num,n); // cout<<check(3,n)<<" "; // cout<<ans<<endl; int left=0,right=n,mid;//开始二分 while(right>=left){ mid=(right+left)/2; if(check(mid,n)){ //判断长度为mid的串是否重复了m次 left=mid+1; ddd=mid; } else{ right=mid-1; } } //cout<<mid<<endl; if(ddd){ printf("%d %d\n",ddd,ans); } else{ printf("none\n"); } } return 0; }