20200129模拟赛T1 string
题目描述:
分析:
看到题目:后缀数组,二分,加加减减
然后。。。然后。。。
“后缀数组是个好东西,我有头发的时候天天写。”
我们首先要求出二分的区间,可能二分到所有子串字典序编号
二分的过程当中,首先先找出字典序当前值得字符串,这里要用到height数组RMQ
我们现在就需要将所有字典序大于该子串的切掉,从前往后枚举开头i,当LCP大于目标串的Len时,说明字典序更大,这时候要将i+Len位置切掉
最后通过切的次数来判断二分
以上说得很好。。。
真就写20分钟,调两个小时
“后缀数组是个好东西,我有头发的时候天天写。”
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 200005
using namespace std;
inline int getint()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int n,k;
char s[maxn];
int st,len;
int sa[maxn],height[maxn];
int rk[maxn],bit[maxn],f[21][maxn];
int cnt[maxn],x[maxn],y[maxn];
int aa[maxn];
inline void suffix()
{
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++)cnt[int(s[i])]++;
for(int i=1;i<=128;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[int(s[i])]--]=i;
rk[sa[1]]=1;
for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
for(int k=1;rk[sa[n]]!=n;k<<=1)
{
for(int i=1;i<=n;i++)
{
x[i]=rk[i];
y[i]=(i+k<=n)?rk[i+k]:0;
}
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++)cnt[y[i]]++;
for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)rk[cnt[y[i]]--]=i;
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++)cnt[x[i]]++;
for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[x[rk[i]]]--]=rk[i];
rk[sa[1]]=1;
for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(x[sa[i]]!=x[sa[i-1]]||y[sa[i]]!=y[sa[i-1]]);
}
int now=0;
for(int i=1;i<=n;i++)
{
if(now)now--;
for(int j=sa[rk[i]+1];s[j+now]==s[i+now];now++);
height[rk[i]]=now;
}
for(int i=1;i<=n;i++)
{
f[0][i]=height[i];
bit[i]=bit[i-1];
if(i>=(1<<(bit[i]+1)))bit[i]++;
}
for(int p=1;p<20;p++)
for(int i=1,j=(1<<(p-1))+1;j<=n;i++,j++)
f[p][i]=min(f[p-1][i],f[p-1][j]);
for(int i=1;i<=n;i++)aa[i]=rk[i];
}
inline int LCP(int x,int y)
{
if(x==y)return n-x+1;
x=rk[x],y=rk[y];
if(x>y)swap(x,y);
int tmp=bit[y-x];
return min(f[tmp][x],f[tmp][y-(1<<tmp)]);
}
inline long long getl()
{
long long ans=1;
for(int i=1;s[sa[i]]!=s[sa[n]];i++)
ans+=n-sa[i]+1-height[i];
return ans;
}
inline long long getr()
{
long long ans=0;
for(int i=1;i<=n;i++)
ans+=n-sa[i]+1-height[i];
return ans;
}
inline void solve(long long num)
{
int pos;
for(pos=1;n-sa[pos]+1<num;pos++)
num-=n-sa[pos]+1-height[pos];
st=sa[pos];len=num;
}
inline bool check(int k)
{
if(len==1)return 0;
int ans=0,ed=n;
for(int i=1;i<=n;i++)
{
int tmp=LCP(i,st);
if(tmp>=len)ed=min(ed,i+len-2);
else if(s[i+tmp]>s[st+tmp])ed=min(ed,i+tmp-1);
if(i==ed)ans++,ed=n;
}
return ans<=k;
}
int main()
{
k=getint();
scanf("%s",s+1);
n=strlen(s+1);
suffix();
for(int i=1;i<=n;i++)rk[i]=aa[i];
long long l=getl(),r=getr();
while(l<r)
{
long long mid=(l+r+1)/2;
solve(mid);
if(check(k))r=mid-1;
else l=mid;
}
solve(l);
s[st+len]=0;
printf("%s\n",s+st);
}