Hetao P2071 打字遊戲 題解 [ 綠 ] [ 最小生成樹 ] [ 動態規劃 ] [ 編輯距離 ]

KS_Fszha發表於2024-09-29

打字遊戲:MST 套 dp 好題。

首先看這個資料範圍,\(O(n^4)\) 把每兩個字串之前的編輯距離求一下很顯然吧。

然後我們觀察一下每一個 node 的性質,發現他要麼自己打完,要麼從別人那裡複製過來。這個就很像一棵樹。

建完樹之後,我們就得到了一個森林。

那麼題目就轉化為,求出一個邊權之和最小的森林,使得所有點都在森林中。

顯然我們可以將森林中的每一個根節點超一個虛擬源點連一條邊,這個邊的邊權是多少?實際上就是空串到他的編輯距離,也就是這個字串的長度。

那麼我們就可以在上面跑 MST 了。

還有一個性質,就是 \(A\)\(B\) 那裡複製過來和 \(B\)\(A\) 那裡複製過來的代價是一樣的,正是因為這個性質,這個東西才是顆樹,因為這樣才是雙向邊。不然我們就得跑一個有向圖 MST 了。有向圖 MST 參考滑雪那題。

時間複雜度 \(O(n^4)\),瓶頸在於求編輯距離。

程式碼:

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
ll n,k,dp[105][105],ans=0,cnt=0;
int f[105];
string s[105];
struct edge{
	int u,v;
	ll w;
}e[100005];
bool cmp(edge x,edge y)
{
	return x.w<y.w;
}
ll cal(string a,string b)
{
	for(int i=0;i<=a.length();i++)for(int j=0;j<=b.length();j++)dp[i][j]=0x3f3f3f3f;
	for(int i=0;i<=a.length();i++)dp[i][0]=i;
	for(int j=0;j<=b.length();j++)dp[0][j]=j;
	for(int i=1;i<=a.length();i++)
	{
		for(int j=1;j<=b.length();j++)
		{
			if(a[i-1]==b[j-1])dp[i][j]=min(dp[i][j],dp[i-1][j-1]);
			dp[i][j]=min(dp[i][j],min(dp[i-1][j]+1,dp[i][j-1]+1));
		}
	}
	return dp[a.length()][b.length()];
}
void init()
{
	for(int i=0;i<=n;i++)f[i]=i;
}
int findf(int x)
{
	if(f[x]!=x)f[x]=findf(f[x]);
	return f[x];
}
void combine(int x,int y)
{
	int fx=findf(x),fy=findf(y);
	f[fx]=fy;
}
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>k;
	s[0]="";
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x>>s[i];
	}
	for(int i=0;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			e[++cnt]={i,j,cal(s[i],s[j])+k*((i!=0)&&(j!=0))};
		}
	}
	sort(e+1,e+cnt+1,cmp);
	init();
	for(int i=1;i<=cnt;i++)
	{
		int u=e[i].u,v=e[i].v;
		ll w=e[i].w;
		int fu=findf(u),fv=findf(v);
		if(fu!=fv)
		{
			combine(fu,fv);
			ans+=w;
		}
	}
	cout<<ans;
	return 0;
}

相關文章