HAOI2015树上染色

题目描述

题目链接

有一棵点数为 \(N\) 的树,树边有边权。

给你一个在 \(0 \sim N\) 之内的正整数 \(K\),你要在这棵树中选择 \(K\) 个点,将其染成黑色,并将其他的 \(N-K\) 个点染成白色。

将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。

问收益最大值是多少!

输入格式

第一行两个整数 \(N,K\)

接下来 \(N-1\) 行每行三个正整数 \(fr,to,dis\),表示该树中存在一条长度为 \(dis\) 的边 \((fr,to)\)

输入保证所有点之间是联通的。

输出格式

输出一个正整数,表示收益的最大值。

数据范围

\(1 \le N \le 2000\),
\(0 \le K \le N\)

输入样例:

5 2
1 2 3
1 5 1
2 3 1
2 4 2

输出样例:

17

样例解释

将点 \(1,2\) 染黑就能获得最大收益。

解题报告

题意理解

原题很清晰,没法简析了。

探索算法

有一说一,第一眼看到这道题目,博主是真的没有认为这道题目的是一个树形DP。

但是,染色的题目,加上最值问题的要求,还是和树形DP联系很大。

那么我们为什么很难看出来呢?可能是因为我太菜了

因为我们的关注点,是一个点,它所提供的贡献。

而不是一条边,所提供的贡献

其实这也是一个难想&常见的思路。

对于树上的题目,求贡献,不是点,就是边至少提高组范畴是这样的

那么如果说,我们按照边来思考的话,这道题目的转移方程还是很有意思的。

算法解析

类似于状态压缩动态规划,树形DP的题目,状态设计是极为重要的一步。

那么如何设计状态呢?

既然上面,我们说了,要按照边来算贡献,那么我们就先来分析边,是如何提供贡献的?

对于一条边而言,他可以提供的贡献,来自于两个节点之间的路径有它,也就是经过它:

  1. 左右两边黑色节点提供的。(自己子树内的黑色节点 $ \times $ 子树外黑色节点。
  2. 左右两边白色节点提供的。(自己子树内的白色节点 $ \times $ 子树外白色节点。

那么此时我们需要确定状态了

\( f[x][i] 表示节点x它到父亲节点的这条边,他的子树有i个染黑节点,此时提供的最大贡献 \)

那么对于一个节点他有多少个染黑节点呢?

不知道?

那么枚举吧。

于是本题成了一个树形背包。

每个节点他的子树有多少个黑色节点(体积),然后算出这条边对答案的贡献(价值)

\[black\_sum=s*(k-s) \\\\ white\_sum=(Size[y]-s)*((n-Size[y])-(k-s)) \\\\ s为当前节点他的子树有多少个染成黑色的节点 \\\\ k为题目给出的有k个节点染成黑色 \\\\ Size[y]表示当前节点他的子树一共有多少个节点 \]

因此本题就这么愉快的解决了,在这里默认,树形背包DP大家都会吧。。。

如果不会的话,可以参照Acwing我曾经的背包DP的讲义。

参考代码

//My English is poor.The Code maybe have some grammer problems.
//To have a better Font display in Acwing.com,I must choose English.
#include <bits/stdc++.h>
using namespace std;
const int N=2100;
int n,k,head[N<<1],edge[N<<1],Next[N<<1],tot,Size[N];
long long f[N][N],ver[N<<1];
inline void add_edge(int a,int b,int c)
{
    edge[++tot]=b;
    ver[tot]=c;
    Next[tot]=head[a];
    head[a]=tot;
}
void tree_DP(int x,int fa)
{
    Size[x]=1;
    memset(f[x],-1,sizeof(f[x]));
    f[x][0]=f[x][1]=0;
    for(int i=head[x]; i; i=Next[i])//visit its sons
    {
        int y=edge[i];
        if (y==fa)//its father
            continue;//can't visit its father node
        tree_DP(y,x);//visit the son
        Size[x]+=Size[y];//count the Size of the tree
    }
    //Next part is the DP whose kind is knapsack.
    for(int i=head[x]; i; i=Next[i])
    {
        int y=edge[i];
        if (y==fa)//its father
            continue;//can't visit its father node
        for(int j=min(k,Size[x]); j>=0; j--)//choose black nodes whose number is j
        {
            for(int s=0; s<=min(j,Size[y]); s++)
                if (~f[x][j-s])
                {
                    long long black_sum=s*(k-s);//balck pairs
                    long long white_sum=(Size[y]-s)*((n-Size[y])-(k-s));//white pairs
                    f[x][j]=max(f[x][j],f[x][j-s]+f[y][s]+(black_sum+white_sum)*ver[i]);
                }
        }
    }
}
inline void init()
{
    scanf("%d%d",&n,&k);
    for(int i=1; i<n; i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add_edge(a,b,c);
        add_edge(b,a,c);
    }
    tree_DP(1,0);
    printf("%lld\n",f[1][k]);
}
signed main()
{
    init();
    return 0;
}
posted @ 2020-12-29 17:31  秦淮岸灯火阑珊  阅读(359)  评论(0编辑  收藏  举报