题意
给定 \(n\) 个字符串 \(s_i\),你需要选择若干个字符串按从前往后的顺序拼起来使得总长为 \(k\) 且字典序最小,保证有解。
\(n\le 2000,k\le 10^4,\sum |s_i|\le 10^6\)
分析
先考虑一个显然的暴力 DP:设 \(f_{i,j}\) 表示前 \(i\) 个串总长为 \(j\) 的最小字典序(注意这里由于限制了长度所以无需倒序转移),复杂度 \(O(nk^2)\),无法通过。
考虑优化,发现很多状态实际上是不会成为最优状态的。
首先做一个简单的小剪枝:预处理 \(g_{i,j}\) 表示用 \([i,n]\) 中的串能否拼出长为 \(k\) 的字符串,计算的复杂度是 \(O(\frac{nk^2}{w})\),那么我们在转移 \(f_{i,j}\) 时如果 \(g_{i+1,k-j}=0\) 那么状态 \(f_{i,j}\) 不合法,必然不会成为最优状态。
因为我们需要的是字典序最小,而如果一个方案在转移前就存在某一位比另一个方案大,那么前一种方案就是没用的。不难发现,对于一个 \(i\),所有可能成为最优状态的 \(f_{i,*}\) 互为前缀关系。把长度最大的那个 \(f_{i,*}\) 取出来,设为 \(t_i\),那么所有的 \(f_{i,*}\) 都是 \(t_i\) 的一段前缀。那么在 \(i\) 之下的所有合法状态均可以用一个数 \(j\) 表示。
考虑转移。把 \(f_{i-1,*}\) 的合法状态压成一个单调栈,从顶到底长度递降排序。令 \(b_j=f_{i-1,j}+s_i\),显然 \(b_i\) 的数量等于栈大小。考虑把一个 \(b_i\) 压进栈,若 \(b_i\) 的字典序比栈顶的字典序要大,那么这个 \(b_i\) 就废了;若比栈顶小,那么弹出栈顶,直到栈顶是 \(b_i\) 的前缀或者栈为空(注意不会发生比栈顶字典序大的情况,否则会在初始被废掉)。
现在我们就把时间复杂度降为了 \(O(nkT(|S|))\),其中 \(T(|S|)\) 是处理字符串比较的时间复杂度。发现我们在比较字符串字典序大小时比较的是 \(f_{i,*}+b_*\) 之间的大小关系,后面的 \(b_*\) 可以为空。设要比较的是 \(s_1=f_{i,x}+b_i\) 和 \(s_2=f_{i,y}+b_i\),设 \(x<y\),由于 \(f_{i,*}\) 是 \(t_i\) 的一段前缀,所以在 \([1,x]\) 这段前缀上两串相等,问题转化为 \(b_i\) 和 \(t_i[x+1,y]\) 比较大小,若两者相等再比较 \(b_i[y-x,\cdots]\) 和 \(b_i\) 的大小。发现这就是 Z 函数模板题(求字符串与本身每个后缀和另一个串的每个后缀的 LCP)。使用 Z 函数做到 \(T(|S|)=O(1)\) 或者偷懒用字符串哈希做到 \(T(|S|)=O(\log |S|)\),可以通过。
代码使用字符串哈希。里面有巨量注释及调试痕迹,谨慎阅读。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<map>
#include<unordered_map>
#include<vector>
#include<queue>
#include<stack>
#include<bitset>
#include<set>
#include<ctime>
#include<random>
#include<cassert>
#define x1 xx1
#define y1 yy1
#define IOS ios::sync_with_stdio(false)
#define ITIE cin.tie(0)
#define OTIE cout.tie(0)
#define PY puts("Yes")
#define PN puts("No")
#define PW puts("-1")
#define P0 puts("0")
#define P__ puts("")
#define PU puts("--------------------")
#define mp make_pair
#define fi first
#define se second
#define gc getchar
#define pc putchar
#define pb emplace_back
#define un using namespace
#define all(x) x.begin(),x.end()
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
#define reprange(a,b,c,d) for(int a=(b);a<=(c);a+=(d))
#define perrange(a,b,c,d) for(int a=(b);a>=(c);a-=(d))
#define graph(i,j,k,l) for(int i=k[j];i;i=l[i].nxt)
#define lowbit(x) (x&-x)
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
#define mem(x,y) memset(x,y,sizeof x)
//#define double long double
//#define int long long
//#define int __int128
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
using pii=pair<int,int>;
bool greating(int x,int y){return x>y;}
bool greatingll(long long x,long long y){return x>y;}
inline int rd(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}return x*f;
}
inline void write(int x,char ch='\0'){
if(x<0){x=-x;putchar('-');}
int y=0;char z[40];
while(x||!y){z[y++]=x%10+48;x/=10;}
while(y--)putchar(z[y]);if(ch!='\0')putchar(ch);
}
bool Mbg;
const int maxn=2e3+5,maxm=1e4+5,inf=0x3f3f3f3f;
const bool zgc_ak_ioi=true;
const int mod=787838027,base=59;
const long long llinf=0x3f3f3f3f3f3f3f3f;
int n,k;
string s[maxn];
bitset<maxm>g[maxn];
string t[maxn];
//tv[i]:f[i-1][j]是否有值
bool tv[maxm];
//(前缀长度,是否接上i)
pii sta[maxm];
int tp;
//f[i][j]的表示
pii b[maxm];
int h[2][maxm];
int mi[maxm];
int hi[maxm];
int z[maxm];
//hi[i] LCP(str,str[i~end])
//z[i] LCP(str,txt[i~end])
inline int get(int l,int r){
return (h[1][r]-(l?(1ll*h[1][l-1]*mi[r-l+1]%mod):0)+mod)%mod;
}
inline int __get__(int l,int r){
return (h[0][r]-(l?(1ll*h[0][l-1]*mi[r-l+1]%mod):0)+mod)%mod;
}
void init(string str,string txt){
int len1=str.size(),len2=txt.size();
h[0][0]=str[0]-'a';
rep(i,1,len1-1)h[0][i]=(1ll*h[0][i-1]*base%mod+str[i]-'a')%mod;
h[1][0]=txt[0]-'a';
rep(i,1,len2-1)h[1][i]=(1ll*h[1][i-1]*base%mod+txt[i]-'a')%mod;
rep(i,0,len1-1){
int l=0,r=len1-1-i,res=0;
while(l<=r){
int mid=(l+r)>>1;
if(h[0][mid]==__get__(i,i+mid))res=mid+1,l=mid+1;
else r=mid-1;
}
hi[i]=res;
}
rep(i,0,len2-1){
int l=0,r=min(len1-1,len2-1-i),res=0;
while(l<=r){
int mid=(l+r)>>1;
if(h[0][mid]==get(i,i+mid))res=mid+1,l=mid+1;
else r=mid-1;
}
z[i]=res;
}
}
//_ 下标
//0 y是x前缀 -1 y<x 1 y>x
/*
case 1:
--------++++++
-----++++++
case 2:
-----------
----+++++
*/
int cmp(pii x,pii y,int _){
int rev=1;
if(x.fi<y.fi)swap(x,y),rev=-1;
if(!y.se)return 0;
int len=s[_].size();
int __len__=y.fi+len;
if(__len__<=x.fi){//case 2
if(z[y.fi]>=len)return 0;
return s[_][z[y.fi]]<t[_-1][y.fi+z[y.fi]]?-rev:rev;
}else{//case 1
if(y.fi+z[y.fi]>=x.fi){
if(!x.se)return 0;
int num=x.fi-y.fi;
if(num+hi[num]>=len)return 0;
return s[_][num+hi[num]]<s[_][hi[num]]?-rev:rev;
}else{
return s[_][z[y.fi]]<t[_-1][y.fi+z[y.fi]]?-rev:rev;
}
}
}
inline void solve_the_problem(){
cin>>n>>k;
rep(i,1,n)cin>>s[i];
mi[0]=1;rep(i,1,k)mi[i]=1ll*mi[i-1]*base%mod;
g[n+1][0]=1;
per(i,n,1)g[i]=g[i+1]|(g[i+1]<<s[i].size());
// rep(i,1,n){
// write(i,10);
// rep(j,0,k)write(g[i][j],32);
// P__;
// }
tv[0]=1;
rep(_,1,n){
init(s[_],t[_-1]);
int len=s[_].size();
// write(_,10);
// rep(i,0,k)write(tv[i]);P__;
rep(i,1,k)b[i]=mp(-1,-1);
rep(i,1,k){
if(tv[i])b[i]=mp(i,0);
if(i>=len&&tv[i-len]){
if(b[i]==mp(-1,-1)||(z[i-len]<len&&s[_][z[i-len]]<t[_-1][i-len+z[i-len]])){
b[i]=mp(i-len,1);
}
}
}
tp=0;
rep(i,1,k)if(b[i]!=mp(-1,-1)&&g[_+1][k-i]){
while(zgc_ak_ioi){
if(!tp){
sta[++tp]=b[i];
break;
}
int cp=cmp(b[i],sta[tp],_);
if(!cp){
sta[++tp]=b[i];
break;
}
if(cp==-1)break;
--tp;
}
}
// write(_,10);
// rep(i,1,tp)cerr<<t[_-1].substr(0,sta[i].fi)+(sta[i].se?s[_]:"")<<endl;
// PU;
t[_]=t[_-1].substr(0,sta[tp].fi)+(sta[tp].se?s[_]:"");
tv[0]=1;rep(i,1,k)tv[i]=0;
rep(i,1,tp)tv[sta[i].fi+sta[i].se*len]=1;
}
// rep(i,1,n)cerr<<t[i]<<endl;
cout<<t[n];
}
bool Med;
signed main(){
// freopen(".in","r",stdin);freopen(".out","w",stdout);
// fprintf(stderr,"%.3lfMB\n",(&Mbg-&Med)/1048576.0);
int _=1;IOS;
while(_--)solve_the_problem();
}
/*
5 10
m
mmm
nnnnn
mm
mmnnnnnnn
*/