●赘述题目
给出一个字符串,要求分成k个子串,然后求出每个子串的字典序最大的子串(我称它为子子串),要使这k个子子串中的字典序最大的那个串(即魔力串)最小。输出该魔力串。
(本题个人感觉很好,比较综合。属于后缀数组中等题。)
●题解
方法:后缀数组+RMQ+二分
既然是要“最大的最小”,那很“习惯”地就会想到二分,大体如下
l = 1; r = n; //所有的不同的子串的个数 while ( l<=r ) { mid =( l + r ) / 2; if ( check ( mid ) ) //如果将第mid大的子串作为魔力串,检查是否合法 ans = mid , r =mid - 1; //如果合法,记录答案 else l = mid + 1; }
那么,就面临着几个问题:
1.上述代码中 n 的值多少?(即如何求不同的子串的个数):
“一个串中不同子串的总数=∑(len-height[i]-sa[i])”,用这个式子就可以解决,至于为什么,自己去模拟一下吧。
2.如何找出第mid大的子串? 我相信如果弄懂了上面那个式子,应该会有想法的。
3.如何check(mid)?
给出一种思路:反向遍历原串,用一个start和end分别记录当前的开头和结尾,
若start到end这段子串的字典序大于了第mid大的子串,那么就在start和start-1之间“砍一刀”,并更新start和end,以及累计分成的段数。
若start到end这段子串的字典序小于第mid大的子串,便只更新start的值
最后,看累计的段数与输入k的大小关系,并判断返回true or false
(4.在3中,如何快速比较start到end这段子串与第mid大的子串的大小关系(当然不能逐位比较):
用构建的height数组,建立ST表,用于RMQ快速求出两子串的LCP,然后就自己脑补吧。)
把以上的步骤写成函数,也就差不多完成了。
●值得注意的是,对于该题的数据范围,我们求出的不同的子串的个数n应该会爆int的。无辜的我被这个错误折磨了整整5天。。。。。。
/************************************************************** Problem: 4310 User: Language: C++ Result: Accepted Time:2640 ms Memory:11656 kb ****************************************************************/ #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define ll long long using namespace std; char s[100006],k[100006]; int t1[100006],t2[100006],sa[100006],ra[100006],he[100006],c[100005]; int f[100006][20]; int ls,n,ke,ks,aks=-1,ake=-1; ll l=1,r,mid; void build_array(int m) { int *x=t1,*y=t2,p; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<ls;i++) c[x[i]=s[i]-'!'+1]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=ls-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<=ls;k<<=1) { p=0; for(int i=ls-k;i<ls;i++) y[p++]=i; for(int i=0;i<ls;i++) if(sa[i]>=k) y[p++]=sa[i]-k; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<ls;i++) c[x[y[i]]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=ls-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; swap(x,y); m=2; x[sa[0]]=1; for(int i=1;i<ls;i++) x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])?m-1:m++; if(m>ls) break; } for(int i=0;i<ls;i++) ra[sa[i]]=i; p=0; for(int i=0;i<ls;i++) { if(p) p--; if(ra[i]==0) continue; int j=sa[ra[i]-1]; while(s[i+p]==s[j+p]) p++; he[ra[i]]=p; } } void get_Kth(ll x) { int ng; for(int i=0;i<ls;i++) { ng=ls-he[i]-sa[i]; if(ng<x) x-=ng; else { ng=0; /* for(int j=sa[i];j<=sa[i]+he[i]+x-1;j++) { k[ng++]=s[j]; }*/ ks=sa[i]; ke=sa[i]+he[i]+x-1; /*k[ng]=0;*/ break; } } } void make_ST() { for(int i=0;i<ls;i++) f[i][0]=he[i]; for(int j=1;(1<<j)<ls;j++) for(int i=(1<<j)&&i<ls;i<ls;i++) f[i][j]=min(f[i][j-1],f[i-(1<<(j-1))][j-1]); } int rmq(int x,int y) { if(x>y) swap(x,y); if(x==y) return ls-sa[x]; if(y-1==x) return f[y][0]; int j=log(y-x-1)/log(2); return min(f[y][j],f[x+(1<<j)][j]); } bool check() { int x=ra[ks],y,end=ls-1,k=0; for(int i=ls-1;i>=0;i--) { y=ra[i]; int o=rmq(x,y); o=min(o,min(ke-ks+1,end-i+1)); if(ks+o-1>=ke&&i+o-1>=end) continue; if(ks+o-1<ke&&i+o-1>=end) continue; if(ks+o-1<ke&&i+o-1<end&&s[ks+o]>s[i+o]) continue; k++; end=i; if(s[end]>s[ks]) return 0; } return k+1<=n; } int main() { scanf("%d",&n); l=1; scanf("%s",s); ls=strlen(s); build_array(300); make_ST(); for(int i=0;i<ls;i++) r+=ls-he[i]-sa[i]; while(l<=r) { mid=(l+r)/2; get_Kth(mid); //puts(k); if(check()) aks=ks,ake=ke,r=mid-1; else l=mid+1; } if(!(n>0)) get_Kth(mid),aks=ks,ake=ke; for(int i=aks;i<=ake;i++) putchar(s[i]); puts(""); return 0; }
但程序的效率和排行上的差距不小,希望有好心人能告诉我优化的方法。