分配城市
题目描述:
有2000个城市,是一棵树,树上的边有权值,要求选择k~50个城市,使得城市中所有两两的点的距离的总和最小.
题解:
树形dp,考虑用几个点在这棵树上,对于根节点,为了便于好写,强制要求必须选.那么如果能够搞出来,结果就是枚举所有的作为根节点然后取最小的ans,怎么搞呢?先用一种笨的方法:对于k,枚举给当前子树多少个,然后用更新值,这里有个关键点,当前u,子树v为根节点,给子树m个,那么对答案的影响是:dp[v][m]+2*m*(k-m)*边权,因为之后以一个整体数目看v,那么并不知道具体的分布,直接更新完在v树内部对答案的所有贡献. 还有更快的一点:用树形依赖背包的写法.
重点:
关键是dp[u][k]时,算出k个节点在树u上对答案的所有贡献对:就是从边的角度考虑,之后可以将他们看作一个整体
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(ll i = a;i < b;i++)
#define REP_D(i, a, b) for(ll i = a;i <= b;i++)
typedef long long ll;
using namespace std;
const ll maxn = 2e3 + 10;
const ll INF = 1000000000000ll;
ll dp[maxn][60], n, tot;
struct info
{
ll to, len;
};
vector<info> G[maxn];
void dfs(ll u, ll fa)
{
dp[u][1] = 0;//一定会选根节点
for(ll i = 2; i <= tot; i++)
{
dp[u][i] = INF;
}
REP(i, 0, G[u].size())
{
ll v = G[u][i].to, len = G[u][i].len;
if(v!=fa)
{
dfs(v, u);
for(ll j = tot; j >= 2; j--)//倒着更新
{
for(ll k = 1; k <= j - 1; k++)//考虑到更新时用j-k,k不能是0,并且k不能是j,至少要留一个.不然不好用,或者之前dp[u][0]置成INF.
{
if(dp[u][j-k]!=INF&&dp[v][k]!=INF)
dp[u][j] = min(dp[u][j], dp[u][j-k]+dp[v][k]+2ll*len*k*(tot-k));
}
}
}
}
}
void solve()
{
dfs(1, 0);
ll ans = INF;
for(ll i = 1; i <= n; i++)
{
ans = min(ans, dp[i][tot]);//遍历所有可能的根节点.
}
printf("%I64d\n", ans);
}
int main()
{
//freopen("4Din.txt", "r", stdin);
//freopen("4Dout.txt", "w", stdout);
ll ncase;
scanf("%I64d", &ncase);
while(ncase--)
{
scanf("%I64d%I64d", &n, &tot);
REP_D(i, 1, n)
{
G[i].clear();
}
REP_D(i, 1, n - 1)
{
ll a, b, len;
scanf("%I64d%I64d%I64d", &a, &b, &len);
info t;
t.to = b;
t.len = len;
G[a].push_back(t);
t.to = a;
G[b].push_back(t);
}
solve();
}
return 0;
}