[HAOI2015]樹上染色(樹上揹包)

題目描述

有一棵點數爲 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的黑節點越多,佔用的體積越大,但是我們得到邊的價值,邊的價值怎麼計算呢?

val=j*(k-j)*e.cost+(sz[v]-j)*(n-k-sz[v]+j)*e.cost

前一段爲邊兩端的黑點,後一段爲邊兩端的白點(sz[v]爲子樹v的大小)

所以,我們的轉移方程就出來了

dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[v][j]+val)

表示以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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章