LINK

题意

给定一颗 n n n个节点的边权树,在 k k k个叶子节点建立工厂

令两两工厂的距离和最小,求出最小距离和


定义 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i的子树内选择 j j j个叶子节点的距离和

转移为 f [ u ] [ i ] = min ⁡ { f [ s o n ] [ j ] + f [ u ] [ i − j ] + g [ u ] [ i − j ] ∗ g [ v ] [ j ] } f[u][i]=\min\{f[son][j]+f[u][i-j]+g[u][i-j]*g[v][j]\} f[u][i]=min{f[son][j]+f[u][ij]+g[u][ij]g[v][j]}

其中 g [ u ] [ i ] g[u][i] g[u][i]表示在 u u u的子树内选 j j j个叶子节点到点 u u u的距离和

但是发现 f [ u ] [ i ] f[u][i] f[u][i] g [ u ] [ i ] g[u][i] g[u][i]并不成正比,若 f f f值小但 g g g比较大呢??

在之后的转移是不占优势的,也就是具有后效性,无法转移

考虑对每条边计算贡献,这样就不需要 g g g数组

定义 f [ i ] [ j ] f[i][j] f[i][j]表示 i i i的子树内选择 j j j个叶子节点对全局造成的最小贡献

转移为 f [ u ] [ i ] = min ⁡ { f [ s o n ] [ j ] + f [ u ] [ i − j ] + j ∗ ( k − j ) ∗ w } f[u][i]=\min\{f[son][j]+f[u][i-j]+j*(k-j)*w\} f[u][i]=min{f[son][j]+f[u][ij]+j(kj)w}

其中 w w w ( u , v ) (u,v) (u,v)边的权值

这样没有后效性,类似树形背包那样转移就好了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e6+11;
const int inf = 1e18;
int t,n,k,casenum,siz[maxn],f[100009][109];
typedef pair<int,int>p;
vector<p>vec[maxn]; 
//f[i][j]表示i的子树内选了j个工厂 
//那么需要知道j个工厂到节点i的距离和 
void dfs(int u,int fa)
{
	for(int i=1;i<=k;i++)	f[u][i] = inf;
	if( vec[u].size()==1 )	f[u][1] = 0,siz[u] = 1;
	else	siz[u] = 0;
	for( auto x:vec[u] )
	{
		int v = x.first;
		if( v==fa )	continue;
		dfs(v,u);
		for(int i=min(k,siz[u]);i>=0;i--)
		for(int j=min(k-i,siz[v]);j>=0;j--)
			f[u][i+j] = min( f[u][i+j],f[v][j]+f[u][i]+(k-j)*j*x.second );
		siz[u] += siz[v];
	}
}
signed main()
{
	cin >> t;
	while( t-- )
	{
		cin >> n >> k;
		for(int i=1;i<n;i++)
		{
			int l,r,w; scanf("%lld%lld%lld",&l,&r,&w);
			vec[l].push_back( {r,w} ), vec[r].push_back( {l,w} );
		}
		dfs(1,0);
		cout << "Case #" << ++casenum << ": ";
		cout << f[1][k] << endl;
		for(int i=1;i<=n;i++)	vec[i].clear();
	}
}