链接 : [HAOI2015]树上染色
题目描述
有一棵点数为 n 的树,树边有边权。给你一个在 0∼n 之内的正整数 k ,你要在这棵树中选择 k 个点,将其染成黑色,并将其他 的 n−k 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入格式
第一行包含两个整数 n,k。
第二到 n 行每行三个正整数 fr,to,dis,表示该树中存在一条长度为 dis 的边 (fr, to)。输入保证所有点之间是联通的。
输出格式
输出一个正整数,表示收益的最大值。
输入输出样例
输入 #1
3 1
1 2 1
1 3 2
输出 #1
3
说明/提示
对于 100% 的数据,0≤n,k≤2000。
Solution
把路径拆分成边,记录一下每条边被经过的次数,求任意两白点/两黑点通过此条边的贡献。
令dp[i][j]为以i为根的子树中选择j个黑节点对答案的最大贡献,然后做树上背包即可。
细节处理:
1.每条边的贡献必须是选择k个黑节点时的贡献,所以有一些非法情况需要排除,即当前子树选择x个黑节点,剩余子树不足k - x个黑节点的情况。
2.第二层for循环必须先把k == 0 的情况先处理,否则f[u][j]会在之前的转移中被更新,我们所需要原状态已经被更新掉了。
代码
#include <bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long ll;
const int SZ = 2000 + 10;
ll dp[SZ][SZ];
struct zt
{
int v;
int w;
int nxt;
}line[SZ * 2];
int fist[SZ],temp,n,k,siz[SZ];
inline void build(int x,int y,int z)
{
line[++ temp] = (zt){y,z,fist[x]};
fist[x] = temp;
}
inline void dfs(int u,int far)
{
siz[u] = 1;
dp[u][0] = dp[u][1] = 0;
for(RI i = fist[u];i;i = line[i].nxt)
{
int v = line[i].v;
if(v == far) continue;
dfs(v,u);
siz[u] += siz[v];
for(RI j = min(k,siz[u]);j >= 0;j --)
for(RI p = 0;p <= min(siz[v],j);p ++)
{
if(dp[u][j - p] == -1) continue;
ll tot = 1ll * line[i].w * p * (k - p) + 1ll * line[i].w * (siz[v] - p) * (n - k - siz[v] + p);
dp[u][j] = max(dp[u][j],dp[u][j - p] + dp[v][p] + tot);
}
}
}
int main()
{
memset(dp,-1,sizeof(dp));
scanf("%d%d",&n,&k);
int a,b,c;
for(RI i = 1;i < n;i ++)
{
scanf("%d%d%d",&a,&b,&c);
build(a,b,c);
build(b,a,c);
}
dfs(1,1);
printf("%lld\n",dp[1][k]);
return 0;
}
2020.4.29