[題解][2021-2022年度國際大學生程式設計競賽第10屆陝西省程式設計競賽] Type The Strings

ZWZWW發表於2024-04-15

題目描述

給定n個字串,有以下幾種操作:

  • 打出一個字元,花費1。
  • 刪除一個字元,花費1。
  • 複製並打出一個之前打出過的字串,花費k。

求打出所有n個字串的最小花費。
(注意,打出順序和字串輸入的順序不必相同)

題解
顯然,操作3需要算字串的最長公共子序列來處理。
這個問題可以轉換為最小生成樹問題:

  1. 目前已經打出的字串可以被視為最小生成樹中已經加入的節點。
  2. 對於每個字串,都有兩種方式:直接花費length的代價或透過別的點得到。
  3. 對於第一個字串,一定會直接花費length的代價得到。
  4. 那麼,就可以設一個虛點0,向所有點建立權值為各自length的邊。
    對於每兩個點,建立一條權值為length[x]+length[y]-lcs(x,y)*2+k的邊。
    由於至少有一個字串會直接花費length打出,那麼虛點一定會被加入到最小生成樹中。
    即最小生成樹滿足該題的性質。

程式碼

#include<bits/stdc++.h>
#define  int  long long
using namespace std;
const int N=107;
int l[N],f[N][N],fa[N],en=0,n,k;
string s[N];
struct Edge{
	int u,v,w;
}e[N*N*N];
bool cmp(Edge x,Edge y){
	return x.w<y.w;
}
void adde(int u,int v,int w){
	e[++en].u=u,e[en].v=v,e[en].w=w;
}
int find(int x){
	if(fa[x]==x)return x;
	return fa[x]=find(fa[x]);
}
int lcs(int x,int y){
	for(int i=0;i<=l[x];i++)
		for(int j=0;j<l[y];j++)
			f[i][j]=0;
	for(int i=1;i<=l[x];i++)
		for(int j=1;j<=l[y];j++){
			if(s[x][i-1]==s[y][j-1]){
				f[i][j]=f[i-1][j-1]+1;
			}
			else{
				f[i][j]=max(f[i-1][j],f[i][j-1]);
			}
		}
	return k+l[x]+l[y]-f[l[x]][l[y]]*2;
}
signed main(){
	int ans=0;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		fa[i]=i;
		cin>>l[i];
		cin>>s[i];
		adde(0,i,l[i]);
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			adde(i,j,lcs(i,j));
	sort(e+1,e+en+1,cmp);
	for(int i=1;i<=en;i++){
		int u=find(e[i].u),v=find(e[i].v);
		if(u==v)continue;
		fa[v]=u;
		ans+=e[i].w;
	}
	cout<<ans<<endl;
	return 0;
}

相關文章