[ARC058F] 文字列大好きいろはちゃん

dcytrl發表於2024-10-07

題意

給定 \(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
*/

相關文章