NC19996 [HAOI2015]树上染色
题目
题目描述
有一棵点数为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\) 个点染黑后对答案的最大总贡献,注意这里是子树对答案的贡献,而非子树内的贡献。如果只是子树内的贡献,会发现转移时不知道点的具体位置,从而不能计算权值的变化,无法转移;而计算子树对整个答案的贡献就不需要考虑内点位置,而只要考虑父节点与子树根节点连的那条边的权值与子树染黑节点的数量即可转移。转移方程为:
\]
其中, \(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\) 时,会发现转移方程变为:
\]
直接原地更新了,这意味着更新 \(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]树上染色的更多相关文章
- bzoj 4033: [HAOI2015]树上染色 [树形DP]
		
4033: [HAOI2015]树上染色 我写的可是\(O(n^2)\)的树形背包! 注意j倒着枚举,而k要正着枚举,因为k可能从0开始,会使用自己更新一次 #include <iostream ...
 - BZOJ4033: [HAOI2015]树上染色(树形DP)
		
4033: [HAOI2015]树上染色 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3461 Solved: 1473[Submit][Stat ...
 - BZOJ4033 HAOI2015 树上染色 【树上背包】
		
BZOJ4033 HAOI2015 树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白 ...
 - [BZOJ4033][HAOI2015]树上染色(树形DP)
		
4033: [HAOI2015]树上染色 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2437 Solved: 1034[Submit][Stat ...
 - 【BZOJ4033】[HAOI2015]树上染色 树形DP
		
[BZOJ4033][HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染 ...
 - BZOJ_4033_[HAOI2015]树上染色_树形DP
		
BZOJ_4033_[HAOI2015]树上染色_树形DP Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的 ...
 - BZOJ 4033[HAOI2015] 树上染色(树形DP)
		
4033: [HAOI2015]树上染色 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3188 Solved: 1366[Submit][Stat ...
 - [HAOI2015]树上染色(树形dp)
		
[HAOI2015]树上染色 题目描述 有一棵点数为 N 的树,树边有边权.给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 . 将所 ...
 - [HAOI2015]树上染色(树上dp)
		
[HAOI2015]树上染色 这种要算点对之间路径的长度和的题,难以统计每个点的贡献.这个时候一般考虑算每一条边贡献了哪些点对. 知道这个套路以后,那么这题就很好做了. 状态:设\(dp[u][i]\ ...
 - [HAOI2015]树上染色 树状背包 dp
		
#4033. [HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的N-K个点染成白 ...
 
随机推荐
- Java 有效字符串判断
			
转载请注明出处: 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭合. ...
 - gitee 创建代码仓库,并提交本地代码
			
本文为博主原创,转载请注明出处: 1. 配置本地 gitee 的配置: git config --global user.name "xiangBaxiang" git confi ...
 - Servlet系列:生命周期(init、 service、destroy)详解
			
Servlet的生命周期是由Web容器(如Tomcat)管理的,包括以下三个阶段: 加载和实例化:当Web应用程序启动时,Web容器会加载和实例化Servlet.加载和实例化过程可以在应用程序启动时自 ...
 - UPF - Power Intent Basic
			
Mainstream Low Power techniques Low Vth - 阈值电压比较低,翻转时间小,漏电流比较大,功耗大,速度快 High Vth - 阈值电压比较高,翻转时间长,漏电流比 ...
 - 【SHELL】查找包含指定字符串的目录、在找出的路径中找出指定格式的文件、并统计出数量
			
查找包含字符串"skull"的目录.在找出的路径中找出格式".c/.cpp/.h"的文件.并统计出数量 find . -path ./out -prune -o ...
 - [转帖]Linux内存管理(一)——从硬件角度看内存管理
			
从硬件角度看内存管理 ①.在操作系统还没有出来之前,程序都是被存放在卡片上,计算机读取一张卡片就运行一条指令.这种从外部存储介质上直接运行指令的方法效率很低 ②.单道编程的内存管理 所谓单道,就是整个 ...
 - [转帖]OceanBase 在线与离线安装方式详解
			
各位好,今天给大家带来一篇有关 OceanBase 在线与离线安装方式的解读.首先我们来讨论一下一日常工作中的一些场景,大家经常会遇到以下几种情况: 公司网络条件很不错,在线下载速度很快,安装软件直接 ...
 - [转帖]认识目标文件的格式——a.out COFF PE ELF
			
https://cloud.tencent.com/developer/article/1446849 1.目标文件的常用格式 目标文件是源代码编译后未进行链接的中间文件(Windows的.obj ...
 - [转帖] Linux查看日志文件写入速度的4种方法
			
https://www.cnblogs.com/codelogs/p/16365448.html 简介# 有时,我们需要查看某个文件的增长速度,如日志文件,以此来感受系统的负载情况,因为一般情况下,日 ...
 - 学习MySQL中DDL语句的修改字段与删除字段,删除表
			
连接本地mysql语句 mysql -hlocalhost -uroot -proot 显示表结构 语法:desc 表名 查看某一个表结构以及注释 语法:show create table 表名 sh ...