題目描述
有一棵點數爲 N 的樹,樹邊有邊權。給你一個在 0~ N 之內的正整數 K ,你要在這棵樹中選擇 K個點,將其染成黑色,並將其他 的N-K個點染成白色 。 將所有點染色後,你會獲得黑點兩兩之間的距離加上白點兩兩之間的距離的和的受益。問受益最大值是多少。
輸入格式
第一行包含兩個整數 N, K 。接下來 N-1 行每行三個正整數 fr, to, dis , 表示該樹中存在一條長度爲 dis 的邊 (fr, to) 。輸入保證所有點之間是聯通的。k<=n<=2000
輸出格式
輸出一個正整數,表示收益的最大值。
思路
標準的樹上揹包的題目。
當我們考慮兩兩點之間的距離和時,我們通常考慮邊,一個邊對答案的貢獻,就是邊兩邊黑白兩種點的個數乘積,這樣計算貢獻就會方便很多。
設dp(i,j)爲以i爲根節點的子樹上,選擇j個點染色,對答案的貢獻的最大值。
那麼問題就變成了樹形揹包了,對於我們在訪問的節點u,以及他的一個直接相連的子節點v,我們把邊(u,v,cost)當作價值,給子節點v的子樹分配黑節點數當作體積,這樣是不是很像一個揹包問題了:分給子樹v的黑節點越多,佔用的體積越大,但是我們得到邊的價值,邊的價值怎麼計算呢?
前一段爲邊兩端的黑點,後一段爲邊兩端的白點(sz[v]爲子樹v的大小)
所以,我們的轉移方程就出來了
表示以u爲根節點的子樹,容量爲i(可以染i個黑點),這些容量可以給他的子節點v分配下去,如果給其中一個子節點v分配j個黑節點,那麼就會獲得val的貢獻(通過(u,v,cost)這條邊計算得到)
整個過程應該就很明瞭了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2000+20;
typedef long long ll;
struct Edge{
int u,v,next;
ll cost;
}edges[maxn<<1];
int head[maxn],tot;
ll dp[maxn][maxn];//i爲根節點的子樹分配j個黑點的最大貢獻
void init(){
memset(head,-1,sizeof(head));
tot=0;
}
void add_edges(int u,int v,ll cost){
edges[tot].u=u,edges[tot].v=v;
edges[tot].cost=cost;
edges[tot].next=head[u];
head[u]=tot++;
}
int n,k;
int sz[maxn];//子樹大小
void dfs(int u,int fa){
sz[u]=1;
memset(dp[u],-1,sizeof(dp[u]));
dp[u][0]=dp[u][1]=0;
//推sz,沒啥好說的
for(int i=head[u];i!=-1;i=edges[i].next){
int v=edges[i].v;
if(v==fa)continue;
dfs(v,u);
sz[u]+=sz[v];
}
for(int i=head[u];i!=-1;i=edges[i].next){
int v=edges[i].v;
if(v==fa)continue;
//現在考慮給u的子節點v分配多少個黑點
for(int x=min(k,sz[u]);x>=0;x--){//x是子樹u分配的黑點
for(int y=0;y<=min(x,sz[v]);y++){//y是u分給子樹v的黑點個數
if(dp[u][x-y]!=-1){
//val是這樣分配情況下,這條邊的貢獻
ll val=y*(k-y)*edges[i].cost+(sz[v]-y)*(n-k-sz[v]+y)*edges[i].cost;
//轉移,類似於揹包
dp[u][x]=max(dp[u][x],dp[u][x-y]+val+dp[v][y]);
}
}
}
}
}
signed main(){
scanf("%lld%lld",&n,&k);
init();
for(int i=1;i<n;i++){
int u,v;
ll cost;
scanf("%lld%lld%lld",&u,&v,&cost);
add_edges(u,v,cost);
add_edges(v,u,cost);
}
dfs(1,-1);
printf("%lld\n",dp[1][k]);
return 0;
}