我们仍然考虑二分子串。设当前二分的子串从位置\(pos\)开始,长度为\(len\)。考虑如何编写check
函数。
一个naive的想法便是从前往后枚举所有极大的不存在小于二分串的子串的段,然后将该段数与规定段数作比较。
但是这有点问题——我们发现,这样做每次都是为段中所有后缀各添加一个字符,不好快速判断。但我们可以借鉴XIV.[SDOI2016]生成魔咒的思想,反过来倒序枚举极大段,这次每次都只是增加一个前缀了。
于是只要判断该前缀与二分串的字典序关系,如果字典序大于二分串就切一刀即可。
因为所有子串数是\(O(n^2)\)的,所以实际复杂度为\(O(n\log n^2)=O(n\times2\log n)=O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100100;
typedef long long ll;
ll sum[N];
int K;
namespace Suffix_Array{
int x[N],y[N],buc[N],sa[N],ht[N],rk[N],n,m,LG[N],mn[N][20];
char s[N];
bool mat(int a,int b,int k){
if(y[a]!=y[b])return false;
if((a+k<n)^(b+k<n))return false;
if((a+k<n)&&(b+k<n))return y[a+k]==y[b+k];
return true;
}
void SA(){
for(int i=0;i<n;i++)buc[x[i]=s[i]]++;
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n-1;i>=0;i--)sa[--buc[x[i]]]=i;
for(int k=1;k<n;k<<=1){
int num=0;
for(int i=n-k;i<n;i++)y[num++]=i;
for(int i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
for(int i=0;i<=m;i++)buc[i]=0;
for(int i=0;i<n;i++)buc[x[y[i]]]++;
for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
for(int i=n-1;i>=0;i--)sa[--buc[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=num=0;
for(int i=1;i<n;i++)x[sa[i]]=mat(sa[i],sa[i-1],k)?num:++num;
if(num>=n-1)break;
m=num;
}
for(int i=0;i<n;i++)rk[sa[i]]=i;
for(int i=0,k=0;i<n;i++){
if(!rk[i])continue;
if(k)k--;
int j=sa[rk[i]-1];
while(i+k<n&&j+k<n&&s[i+k]==s[j+k])k++;
ht[rk[i]]=k;
}
for(int i=2;i<n;i++)LG[i]=LG[i>>1]+1;
for(int i=1;i<n;i++)mn[i][0]=ht[i];
for(int j=1;j<=LG[n-1];j++)for(int i=1;i+(1<<j)-1<n;i++)mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
}
int LCP(int x,int y){
if(x==y)return n-x;
x=rk[x],y=rk[y];
if(x>y)swap(x,y);
x++;
int k=LG[y-x+1];
return min(mn[x][k],mn[y-(1<<k)+1][k]);
}
}
using namespace Suffix_Array;
bool che(ll ip){
int pos=lower_bound(sum,sum+n,ip)-sum,len=ip-sum[pos-1]+ht[pos];
pos=sa[pos];
// printf("%lld:%d,%d\n",ip,pos,len);
int k=0;
for(int i=n-1,j=n-1;i>=0;i=j,k++){//numerate backwards
for(;j>=0;j--){
int tmp=min({LCP(j,pos),i-j+1,len});//the minimum length possible
if(tmp==i-j+1&&tmp<=len)continue;//is ok
if(tmp==len)break;
if(rk[j]>rk[pos])break;//a greater lexicographicity, break.
}
if(i==j)return false;//there's a SINGLE CHARACTER that is lexicographically greater than substring checked, so break out.
}
return k<=K;
}
int main(){
scanf("%d%s",&K,s),n=strlen(s),m='z',SA();
// for(int i=0;i<n;i++)printf("%d ",sa[i]);puts("");
sum[0]=n-sa[0];
for(int i=1;i<n;i++)sum[i]=sum[i-1]+(n-sa[i])-ht[i];
// for(int i=0;i<n;i++)printf("%2d ",i);puts("");
// for(int i=0;i<n;i++)printf("%2d ",s[i]-'a');puts("");
// for(int i=0;i<n;i++)printf("%d ",sum[i]);puts("");
ll l=1,r=sum[n-1];
while(l<r){
ll mid=(l+r)>>1;
if(che(mid))r=mid;
else l=mid+1;
}
int pos=lower_bound(sum,sum+n,l)-sum,len=l-sum[pos-1]+ht[pos];
for(int i=0;i<len;i++)putchar(s[sa[pos]+i]);
return 0;
}
/*
3
ihgjhdgajhdjfjddddfdj
*/