题目链接

题目

题目描述

有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色。

将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。

输入描述

第一行两个整数N,K。

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

输入保证所有点之间是联通的。N ≤ 2000,0 ≤ K ≤ N

输出描述

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

示例1

输入

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

输出

17

说明

【样例解释】

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

备注

对于100% 的数据,\(0 \leq n,k \leq 2000\) 。

题解

知识点:树形dp,背包dp。

这道题长见识了,虽然显然是树上背包,但状态很妙。

设 \(dp[u][i]\) 为以 \(u\) 为根的子树选了 \(i\) 个点染黑后对答案的最大总贡献,注意这里是子树对答案的贡献,而非子树内的贡献。如果只是子树内的贡献,会发现转移时不知道点的具体位置,从而不能计算权值的变化,无法转移;而计算子树对整个答案的贡献就不需要考虑内点位置,而只要考虑父节点与子树根节点连的那条边的权值与子树染黑节点的数量即可转移。转移方程为:

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

其中, \(val = j(m - j) \cdot w + (sz[v] - j)(n - m - (sz[v] - j)) \cdot w\) ,这里我把题目中的 \(K\) 改为了 \(m\) 方便使用。

首先转移方程表示为 \(u\) 为根的子树选了 \(i-j\) 个点染黑,\(v\) 为根的子树选了 \(j\) 个点染黑,并连接 \((u,v)\) 这条边产生 \(val\) 的总贡献是否更大。前面两个没什么问题,考虑 \(val\) 如何计算。

设 \((u,v)\) 的权为 \(w\) ,则 \((u,v)\) 两边的黑节点和白节点的每个组合都能多一次 \(w\) 的贡献。因此黑节点的组合贡献多了 \(j(m-j) \cdot w\) ,因为一端是子树 \(j\) 个黑节点,另一端是其他 \(m-j\) 个黑节点;白节点的组合贡献多了 \((sz[v] - j)(n - m - (sz[v] - j)) \cdot w\) ,因为子树有 \(sz[v]-j\) 个白节点,其他有 \(n-m-(sz[v]-j)\) 个白节点。最后加起来就是 \(val\) 。

到这里整道题算是做完了,但细节上有很多值得注意的。比如 \(j=0\) 时,会发现转移方程变为:

\[dp[u][i] = \max(dp[u][i], dp[u][i] + dp[v][0] + val)
\]

直接原地更新了,这意味着更新 \(j=0\) 时, \(dp[u][i]\) 必须是原来的,这导致 \(j=0\) 必须第一个更新,才能更新别的,正序更新是直接满足这个要求。

除此之外还需要对dp范围进行剪枝,不然铁定超时。这里推荐用刷表法,因为最佳循环条件十分容易就能写出来(即没有浪费一点时间在没用的状态上),打表法不是不能写但非常麻烦,如下面我给出代码就是用打表法写的,虽然这道题用不着这么严格。

剪枝后的复杂度可以证明是 \(O(nm)\) 。

时间复杂度 \(O(nm)\)

空间复杂度 \(O(nm)\)

代码

#include <bits/stdc++.h>
#define ll long long using namespace std; int n, m;
vector<pair<int, int>> g[2007];
int sz[2007];
ll dp[2007][2007]; void dfs(int u, int fa) {
sz[u] = 1;
//dp[u][0] = dp[u][1] = 0;
for (auto [v, w] : g[u]) {
if (v == fa) continue;
dfs(v, u);
sz[u] += sz[v];
for (int i = min(sz[u], m);i >= 0;i--) {
for (int j = max(sz[v] - sz[u] + i, 0);j <= min(i, sz[v]);j++) {
///严格区间可以省掉许多时间(这道题是几百倍),但一般不敢这么严格,通常负无穷区间即可。
///不过这道题用刷表法后,严格区间会很好得到
///这道题需要加两个min限制一下不然会超时,但j的起点max是不必要的。
ll val = 1LL * j * (m - j) * w + 1LL * (sz[v] - j) * (n - m - (sz[v] - j)) * w;
dp[u][i] = max(dp[u][i], dp[u][i - j] + dp[v][j] + val);
///注意j=0时,dp[i][j]会被自己更新,如果j是倒序,会导致之前修改的重复作用,因此j=0必须第一个修改好,其他的顺序随意
}
}
}
} int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1;i < n;i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({ v,w });
g[v].push_back({ u,w });
}
//memset(dp, -0x3f, sizeof(dp));
dfs(1, 0);
cout << dp[1][m] << '\n';
return 0;
}

NC19996 [HAOI2015]树上染色的更多相关文章

  1. bzoj 4033: [HAOI2015]树上染色 [树形DP]

    4033: [HAOI2015]树上染色 我写的可是\(O(n^2)\)的树形背包! 注意j倒着枚举,而k要正着枚举,因为k可能从0开始,会使用自己更新一次 #include <iostream ...

  2. BZOJ4033: [HAOI2015]树上染色(树形DP)

    4033: [HAOI2015]树上染色 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 3461  Solved: 1473[Submit][Stat ...

  3. BZOJ4033 HAOI2015 树上染色 【树上背包】

    BZOJ4033 HAOI2015 树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白 ...

  4. [BZOJ4033][HAOI2015]树上染色(树形DP)

    4033: [HAOI2015]树上染色 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2437  Solved: 1034[Submit][Stat ...

  5. 【BZOJ4033】[HAOI2015]树上染色 树形DP

    [BZOJ4033][HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染 ...

  6. BZOJ_4033_[HAOI2015]树上染色_树形DP

    BZOJ_4033_[HAOI2015]树上染色_树形DP Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的 ...

  7. BZOJ 4033[HAOI2015] 树上染色(树形DP)

    4033: [HAOI2015]树上染色 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 3188  Solved: 1366[Submit][Stat ...

  8. [HAOI2015]树上染色(树形dp)

    [HAOI2015]树上染色 题目描述 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 . 将所 ...

  9. [HAOI2015]树上染色(树上dp)

    [HAOI2015]树上染色 这种要算点对之间路径的长度和的题,难以统计每个点的贡献.这个时候一般考虑算每一条边贡献了哪些点对. 知道这个套路以后,那么这题就很好做了. 状态:设\(dp[u][i]\ ...

  10. [HAOI2015]树上染色 树状背包 dp

    #4033. [HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的N-K个点染成白 ...

随机推荐

  1. 通过设置 Chrome 解决开发调用跨域问题

    转载请注明出处: 项目采用的是前后端分离的方式,前端本地访问方式是 localhost:8080,访问本地后台服务时,通过 localhost:9000 进行访问 本地后端服务.在本地通过chrome ...

  2. Go-命令行参数解析

    1. 解析命令行参数 程序在执行时,获取在命令行启动程序是使用的参数 命令行( Command line interface -- CLI):基于文本来查看.处理.操作计算机的界面,又被称为 终端.控 ...

  3. JS逆向实战27——pdd的anti_content 分析与逆向

    声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 本文已在微信公众号发布 目 ...

  4. oceanbase部署维护命令学习

    oceanbase部署维护命令学习 背景 之前学习过TIDB数据库, 最近又准备学习一下Oceanbase数据库 发现其实两者还是比较相似的. 比较大的区别在于. TiDB是完全开源的, 并且比较明确 ...

  5. [转帖]Linux下清理内存和Cache方法见下文:

    https://www.cnblogs.com/the-tops/p/8798630.html 暂时目前的环境处理方法比较简单: 在root用户下添加计划任务: */10 * * * * sync;e ...

  6. [转帖]Kafka 与RocketMQ 落盘机制比较

    https://www.jianshu.com/p/fd50befccfdd 引言 前几期的评测中,我们对比了Kafka和RocketMQ的吞吐量和稳定性,本期我们要引入一个新的评测标准--软件可靠性 ...

  7. [转帖]springcloud nacos配置

    配置文件中的nacos配置,discovery和config配置项 版本: <spring.boot.version>2.3.2.RELEASE</spring.boot.versi ...

  8. selenium四种截图方式

    1.get_screenshot_as_file() from selenium import webdriver driver = webdriver.Chrome() driver.get(&qu ...

  9. vue3关于.sync的用法

    场景描述 我们都知道,子组件是不能够去修改父组件传递过来的数据. 因为如果子组件去修改父组件件传递过来的数据. 会导致数据的应用流向变得难以理解. 但是有些时候,我们需要当子组件的数据变化后,父组件的 ...

  10. # github突破7k star 即时通讯(IM)开源项目OpenIM每周迭代版本发布

    v2.0已经重构完毕,架构更清晰,代码更规范,邀请各位参与OpenIM社区建设有兴趣的同学可以加我私聊. 目前侧正在业务开发,已提供更多功能,包括群管理,阅后即焚,朋友圈,标签下发等. web端体验: ...