题解:P12454 [INOI Team Selection 2021] String
首先拜谢@Purslane,他把这道题推了出来,并写题解教会了我怎么写这道题。但是我并没有看懂他出的题解,只看懂了他的代码,应该是我太菜了吧。
注意到,字符串的总长度非常小,这启示我们实际上可以直接把所有字符串拼起来考虑。考虑选的过程,显然只有两种情况,一个是在当前串中选的后面再选一个,一个是从下一个串开始选。看起来是很可以贪心的样子。
但是贪心是有问题的,比如说有一个串为
这时 Purslane 就提供了一个思路,实际上可以 dp。这里是一个还算经典的套路,由于字典序问题,每一位基本是独立的,所以不妨设
考虑实现,假设当前要考虑答案串的第
注意边界条件,可以认为没有字符的时候是顶到上界的,不然第一个字符的转移会出问题。
/*胡金梁*/
#include<bits/stdc++.h>
using namespace std;
#define __MY_TEST__ 0
inline int read()
{
int f=1,re=0;
char ch=getchar();
while(!isdigit(ch)){ if(ch=='-') f=-1; ch=getchar();}
while( isdigit(ch)) re=(re<<3)+(re<<1)+(ch^'0'),ch=getchar();
return re*f;
}
int n,k,tot,f[4005][4005][2],bg[4005],tmp[4005][2];
char ans;
string str[4005];
void solve(int num)
{
memset(f[num],0,sizeof f[num]);
memset(tmp,0,sizeof tmp);
for(int i=1;i<=n;i++) for(int j=bg[i];j<bg[i+1];j++) tmp[i][0]|=f[num-1][j][0],tmp[i][1]|=f[num-1][j][1];
if(f[num-1][0][1]) tmp[0][1]=1;
for(int i=1;i<=n;i++) for(int j=bg[i];j<bg[i+1];j++)
{
f[num][j][0]|=tmp[i-1][0];
if(str[i][j-bg[i]]<=ans)
{
f[num][j][str[i][j-bg[i]]==ans]|=tmp[i-1][1];
}
if(j!=bg[i])
{
f[num][j][0]|=f[num-1][j-1][0];
if(str[i][j-bg[i]]<=ans)
{
f[num][j][str[i][j-bg[i]]==ans]|=f[num-1][j-1][1];
}
}
}
}
signed main()
{
#if __MY_TEST__
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
n=read(),k=read();
for(int i=1;i<=n;i++) cin>>str[i],tot+=str[i].size();
bg[1]=f[0][0][1]=1;
for(int i=1;i<=n;i++) bg[i+1]=bg[i]+str[i].size();
for(int num=1;num<=k;num++)
{
int l='a',r='z',Ans;
while(l<=r)
{
int mid=(l+r)>>1;
ans=mid;
solve(num);
bool fg=0;
for(int i=max(1,n+num-k);i<=n;i++) for(int j=bg[i];j<=min(bg[i+1]-1,num+tot-k);j++)
{
fg|=(f[num][j][0]||f[num][j][1]);
}
if(fg) Ans=mid,r=mid-1;
else l=mid+1;
}
ans=Ans;
solve(num);
cout<<ans;
}
}