鏈接 : [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