題目描述
給定n個字串,有以下幾種操作:
- 打出一個字元,花費1。
- 刪除一個字元,花費1。
- 複製並打出一個之前打出過的字串,花費k。
求打出所有n個字串的最小花費。
(注意,打出順序和字串輸入的順序不必相同)
題解
顯然,操作3需要算字串的最長公共子序列來處理。
這個問題可以轉換為最小生成樹問題:
- 目前已經打出的字串可以被視為最小生成樹中已經加入的節點。
- 對於每個字串,都有兩種方式:直接花費length的代價或透過別的點得到。
- 對於第一個字串,一定會直接花費length的代價得到。
- 那麼,就可以設一個虛點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;
}