HAOI2015 树上染色 题解

洛谷题目链接

题意概述:

给定一棵树,任选树上的k个点染成黑点,剩余为白点。

定义这棵树的一个价值为:

一个染色方案中所有颜色相同的点两两之间的最短路径的总和。

求这棵树的最大价值。

Solution

树形dp。

定义状态:

设 dp[u][j] 表示以节点 u 为根节点的子树中染 j 个黑点的最大价值,可知答案为 dp[1][k] 。

转移:

将路径的总和拆分成每一条边对于总和的贡献。对于每一条边的贡献,若边的一边有一个黑点,另一边也有一个黑点,则这条边对于总答案的贡献就会增加一次这条边的权值,显然白点也是一样的。

所以这条边对于总答案的贡献就是:

[ (一边的黑点数量) x (另一边的黑点数量) + (一边的白点数量) x (另一边的白点数量) ] x 这条边的权值

接下来就简单了。将上式之和记为 sum ,则

dp[u][j]=max(dp[u][j],dp[u][j-q]+dp[v][q]+sum);

这个式子中的 u , v 分别为枚举的边连接的两个节点, j 代表以 u 为根节点的子树中选取 j 个子节点染黑, q 则代表以 v 为根节点的子树中选取 q 个子节点染黑。

黑点数量很显然是 q 与 (k-q) ,白点则稍稍复杂。因为树上不是白点就是黑点,所以白点数量是 s[v]-q \(与\) n-k-(s[v]-q) ( 稍稍转化一下就可以得到程序中的 n-k-s[v]+q )。

设 w 为边的权值,那么转移过程中的sum式子就可以表示成:

( q * ( k - q ) + ( s [ v ] - q ) * ( n - k - s [ v ] + q ) ) * w

最后按照思路打一遍代码就行了,注意细节!

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,s[2005],sum,head[2005],cnt_edge,dp[2005][2005];
struct f{
    int next,to,dis;
}edge[4005];
void add(int u,int v,int w){
    edge[++cnt_edge].next=head[u];
    edge[cnt_edge].to=v;
    edge[cnt_edge].dis=w;
    head[u]=cnt_edge;
}
void dfs(int u,int fa){
    s[u]=1;
    //记得初始化
    dp[u][0]=dp[u][1]=0;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].to,w=edge[i].dis;
        if(v==fa)continue;
        dfs(v,u);
        s[u]+=s[v];

        //倒序枚举的原因类似于背包dp,为了防止重复选取
        for(int j=min(k,s[u]);j>=0;j--){

            //一定要正序,除非提前处理好q为0的情况
            for(int q=0;q<=min(j,s[v]);q++){

                //如果是-1就直接continue,否则会T
            	if(dp[u][j-q]==-1)continue;
                sum=(q*(k-q)+(s[v]-q)*(n-k-s[v]+q))*w;
                dp[u][j]=max(dp[u][j],dp[u][j-q]+dp[v][q]+sum);
            }
        }
    }
}
signed main(){
    cin>>n>>k;
    k=min(k,n-k);
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        //无向图连两条边
        add(u,v,w);
        add(v,u,w);
    }
    //初始化为-1方便后续优化
    memset(dp,-1,sizeof dp);
    dfs(1,0);
    cout<<dp[1][k];
    return 0;
}
posted @ 2026-05-03 13:52  fireclouds_11  阅读(15)  评论(0)    收藏  举报