题解:P16103 [ICPC 2019 NAIPC] Cutting Strings
lailai0916 · · 题解
题意简述
从字符串
解题思路
按答案从左到右处理。已经确定的答案前缀保持不动,记尚未处理的原串后缀从位置 pos 开始,剩余删除次数仍为
若
不能简单保留第一次出现的 ababb 且 b 会得到 babb,而删除前缀 aba 可以得到更大的 bb。需要一起考虑答案开头能连续保留多少个
将后缀中每个极长的连续
取走开头免费保留的一块后,其余每个
反过来,任意连接方案都必须删除这些间隔。不同间隔之间隔着保留的字符,不能合并为同一次删除,故连接若干块所需的删除次数恰好等于所选块数。块内没有必要再删字符:保留整个块不会多消耗操作,且能增加开头连续的
设剩余共有
- 若
q\le k ,保留所有块,删除它们前面的间隔,得到\sum_{j=1}^q b_j 个连续的c 。任何少保留一个块的方案都会更早遇到较小字符或结束,因此字典序更小。扣除q 次操作后,继续处理最后一块后面的原串后缀。 - 若
q>k ,最多连接k 个块。首先必须最大化它们的长度和,因此选择长度最大的k 个块。由于每块长度为正,少选一个块不能达到相同的最大和,此时所有删除次数都会用完。
第二种情况中,长度和可能对应多种选择。它们开头连续的
将块长降序排序,记第
由此可以枚举哪一块作为最后选中的块。记最后一个长度大于
前两个条件保证所有必须选中的长块都已出现,且当前块本身可以被选中。第三个条件保证所需的等长块数量足够。若当前块长为
用后缀数组预处理原串所有后缀的字典序排名,在合法的最后一块中,选择块后后缀排名最大的一项。空后缀的排名设为
代码中的 a 保存块长和块后的下标,b 用于排序。lim 对应 lst 对应 sum 从 sum>=k 对应 rnk 使用从
预处理每个后缀的最大字符后,每次完整连接所有块都会使剩余后缀的最大字符严格下降。若当前开头的最大字符块已经包含后缀中全部最大字符,也直接进入下一轮。小写字母至多有
参考代码
#include <bits/stdc++.h>
using namespace std;
using pii=pair<int,int>;
const int N=100005;
int sa[N],rnk[N*2],tmp[N*2],c[N],b[N];
pii a[N];
char mx[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--)
{
int k;
string s;
cin>>k>>s;
int n=s.size();
mx[n]=0;
for(int i=n-1;i>=0;i--)mx[i]=max(s[i],mx[i+1]);
string ans;
int pos=0;
while(pos<n)
{
if(!k){ans+=s.substr(pos);break;}
char ch=mx[pos];
while(pos<n&&s[pos]==ch)ans+=s[pos++];
int cnt=0;
for(int i=pos;i<n;i++)
{
if(s[i]!=ch)continue;
int j=i;
while(i<n&&s[i]==ch)i++;
cnt++;
a[cnt]={i-j,i};
b[cnt]=i-j;
}
if(!cnt)continue;
if(cnt<=k)
{
for(int i=1;i<=cnt;i++)ans.append(a[i].first,ch);
k-=cnt;
pos=a[cnt].second;
continue;
}
sort(b+1,b+cnt+1,greater<int>());
for(int i=1;i<=k;i++)ans.append(b[i],ch);
int lim=b[k],lst=0,sum=0;
for(int i=1;i<=cnt;i++)
{
if(a[i].first<=lim)continue;
lst=i;
sum++;
}
fill(rnk,rnk+2*n+1,0);
fill(tmp,tmp+2*n+1,0);
fill(c,c+27,0);
for(int i=1;i<=n;i++)c[rnk[i]=s[i-1]-'a'+1]++;
for(int i=1;i<=26;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[rnk[i]]--]=i;
int m=26;
for(int i=1;i<n;i<<=1)
{
int len=0;
for(int j=n-i+1;j<=n;j++)tmp[len++]=j;
for(int j=1;j<=n;j++)if(sa[j]>i)tmp[len++]=sa[j]-i;
fill(c,c+m+1,0);
for(int j=0;j<n;j++)c[rnk[tmp[j]]]++;
for(int j=1;j<=m;j++)c[j]+=c[j-1];
for(int j=n-1;j>=0;j--)sa[c[rnk[tmp[j]]]--]=tmp[j];
copy(rnk,rnk+n+1,tmp);
m=0;
for(int j=1;j<=n;j++)
{
if(j==1||tmp[sa[j]]!=tmp[sa[j-1]]||tmp[sa[j]+i]!=tmp[sa[j-1]+i])m++;
rnk[sa[j]]=m;
}
if(m==n)break;
}
int res=-1;
for(int i=1;i<=cnt;i++)
{
if(a[i].first==lim)sum++;
if(i<lst||a[i].first<lim||sum<k)continue;
if(res==-1||rnk[a[i].second+1]>rnk[res+1])res=a[i].second;
}
ans+=s.substr(res);
break;
}
cout<<ans<<'\n';
}
return 0;
}