[poj 3693]后缀数组+出现次数最多的重复子串
题目链接:http://poj.org/problem?id=3693
枚举长度L,看长度为L的子串最多能重复出现几次,首先,能出现1次是肯定的,然后看是否能出现两次及以上。由抽屉原理,这个子串出现次数>=2,那么必定会覆盖s[0],s[L],s[2L],...中相邻的两个,枚举是哪两个。对于覆盖了这两个的重复子串,它重复的次数就是看这两个后缀向前向后各自最多能匹配到多远。假设向前向后共匹配了长度K,那么重复的次数就是K/L+1。
这里有3个问题.
第一个,为什么先前向后各自匹配就可以了?因为子串长度就是L,枚举的这两个位置的距离也是L,那么这两个位置必定得是相同的。
第二个问题,怎么看向前最多匹配多少?向后的话直接通过height数组即可实现,而向前呢?难道要倒着再做一次后缀数组?(当然也不是不可行)事实上不用这么麻烦,直接考虑我们要求的结果K/L+1。利用整除的特性其实很容易得到这个结果。比如我们向后最多匹配L1。现在我们想知道结果能不能比L1/L+1来的大,怎么办呢?考虑L-L1%L,这个数代表L1至少要加几,才会让结果有所增加。那么显然向前距离在这个数以内的我们都不用检验了,因为即使检验到能匹配也没啥用,对结果没啥影响。所以我们检验一下距离是这个数的两个后缀的匹配长度,如果能匹配就更新一下结果。那么距离在这个数以外的呢?其实也不用检验了,如果说距离在这个数以外的还能让结果增加,那必须得再+L,再+L的话其实不必检验了,因为如果能匹配,在之前的求解(上次的枚举)中已经检验过了。因此,只检验一个位置即可。
第三个问题,怎么得到字典序最小的那一组解。通过sa数组的顺序枚举。不过得稍微优化一下,不能所有长度都尝试,不然会T。
穷举长度 L 的时间是 n,每次计算的时间是 n/L。所以整个做法的时间复杂度是 O(n/1+n/2+n/3+……+n/n)=O(nlogn)。(假设查询最长公共前缀的复杂度是O(1),用rmq预处理可以做到)
#include<algorithm> #include<cstdio> #include<cstring> using namespace std; const int MAXN = 100010; #define F(x) ((x)/3+((x)%3==1?0:tb)) #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2) int wa[MAXN*3],wb[MAXN*3],wv[MAXN*3],wss[MAXN*3]; 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++)wss[i] = 0; for(i = 0; i < n; i++)wss[wv[i]]++; for(i = 1; i < m; i++)wss[i] += wss[i-1]; for(i = n-1; i >= 0; i--) b[--wss[wv[i]]] = a[i]; } void dc3(int *r,int *sa,int n,int m) { int i, j, *rn = r + n; int *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++]; } void da(int str[],int sa[],int rank[],int height[],int n,int m) { for(int i = n; i < n*3; i++) str[i] = 0; dc3(str, sa, n+1, m); int i,j,k = 0; for(i = 0; i <= n; i++)rank[sa[i]] = i; for(i = 0; i < n; i++) { if(k) k--; j = sa[rank[i]-1]; while(str[i+k] == str[j+k]) k++; height[rank[i]] = k; } } int str[MAXN*3],sa[MAXN*3],rk[MAXN],height[MAXN]; int RMQ[MAXN]; int mm[MAXN]; int best[20][MAXN]; void initRMQ(int n) { mm[0]=-1; for(int i=1; i<=n; i++) mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; for(int i=1; i<=n; i++)best[0][i]=i; for(int i=1; i<=mm[n]; i++) for(int j=1; j+(1<<i)-1<=n; j++) { int a=best[i-1][j]; int b=best[i-1][j+(1<<(i-1))]; if(RMQ[a]<RMQ[b])best[i][j]=a; else best[i][j]=b; } } int askRMQ(int a,int b) { int t; t=mm[b-a+1]; b-=(1<<t)-1; a=best[t][a]; b=best[t][b]; return RMQ[a]<RMQ[b]?a:b; } int lcp(int a,int b) { a=rk[a]; b=rk[b]; if(a>b)swap(a,b); return height[askRMQ(a+1,b)]; } char s[MAXN]; int cou; int l; int a[MAXN]; int cnt=0; int main() { int cas=0; while (~scanf("%s",s) && s[0]!='#') { l=strlen(s); for (int i=0; i<l; i++) str[i]=s[i]-'a'+1; str[l]=0; da(str,sa,rk,height,l,30); // for (int i=0;i<l;i++) printf("%d ",rk[i]); for (int i=1;i<=l;i++) RMQ[i]=height[i]; initRMQ(l); cou=1; cnt=0; a[cnt++]=1; for (int L=1; L<=l/2; L++) { for (int j=0; j+L<l; j+=L) { int back=lcp(j,j+L); // printf(":%d:\n",back); if (back/L+1>cou) { cou=back/L+1; cnt=0; a[cnt++]=L; } else if (back/L+1==cou) a[cnt++]=L; int check=L-back%L; if (check!=L && j-check>=0) { int pre=lcp(j-check,j-check+L); // printf(":%d:\n",pre); if (pre/L+1>cou) { cou=pre/L+1; cnt=0; a[cnt++]=L; } else if (pre/L+1==cou) a[cnt++]=L; } } } // printf("::%d\n",cou); int ansp=-1; int ansl=-1; // for (int i=1;i<=l;i++) printf("%d ",sa[i]); for (int i=1; i<=l&&ansp==-1; i++) { int j=sa[i]; for (int k=0; k<cnt&&a[k]<=(l-j)/cou; k++) { if (j+a[k]<l && lcp(j,j+a[k])/a[k]+1>=cou) { // printf("QAQ\n"); ansp=j; ansl=a[k]; break; } } } s[ansp+ansl*cou]=0; // printf("%d %d\n",ansp,ansl); printf("Case %d: %s\n",++cas,s+ansp); } return 0; }