仔细思考后会发现和51nod1677 treecnt有异曲同工之妙

Description

有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并
将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。
问收益最大值是多少。

Input

第一行两个整数N,K。
接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to)。
输入保证所有点之间是联通的。
N<=2000,0<=K<=N

Output

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

Sample Input

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

Sample Output

17
【样例解释】
将点1,2染黑就能获得最大收益。

题目分析

这题目第一眼看上去根本不像是背包题吧……倒像是个贪心或者奇妙结论题。

但是可以像treecut一样,将答案按照树上每一条边来统计贡献。

我们把一颗树沿某条边分开,看成这个样子。

那么显然若知道这条边左右两边黑白点各有多少个,就可以计算这个情况下的答案了。

也就是说,如果我们确定一条边来把树分开,那就可以依靠枚举来确定最优答案。

观察一下这个问题是具有最优子结构的,也就是说变成了一个树上背包的形式:左右两边黑白点个数的不同情况各有体积和价值,求最大价值。

我们定义$f[x][i]$表示以$x$为根的子树中,有$i$个黑点,这种情况的最大价值。

考虑如何转移。自然是要枚举上图中的$i$和$j$。然后就是背包式地转移:

 for (int j=mn; j>=; j--)
for (int l=; l<=upp&&l<=j; l++)
{
ll left = 1ll*l*(k-l)*w;
ll right = 1ll*(size[v]-l)*(n-size[v]-k+l)*w;
f[x][j] = std::max(f[x][j], f[v][l]+f[x][j-l]+left+right);
}

其中mn和upp表示枚举的上界。

这个把统计转化为各个边的贡献,然后转为背包做还是很妙的。

总结

一类难以通过树形结构直接转移的动态规划问题,可以考虑对于边将树划分为两个部分的子问题,再分别维护答案。

后记

隔了几天回来看又作为一个不会做这题的人表示讲的有一点不详细。

首先有一个很大的疑问是:讲是把答案拆开统计,但是到底怎么计算这个“贡献”?这个“贡献”又是什么东西?

没写过题的人看了代码肯定又有疑问:为什么统计一下点数再乘个边权就行了?

这里放张图,备忘一下

 #include<bits/stdc++.h>
typedef long long ll;
const int maxn = ;
const int maxm = ;
const ll INF = 112921504606846976ll; struct Edge
{
int y,val;
Edge(int a=, int b=):y(a),val(b) {}
}edges[maxm];
int n,k;
int size[maxn];
int edgeTot,nxt[maxm],head[maxn];
ll f[maxn][maxn]; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void dfs(int x, int fa)
{
size[x] = ;
for (int i=head[x]; i!=-; i=nxt[i])
if (edges[i].y!=fa) dfs(edges[i].y, x), size[x] += size[edges[i].y];
}
void addedge(int u, int v, int w)
{
edges[++edgeTot] = Edge(v, w), nxt[edgeTot] = head[u], head[u] = edgeTot;
}
void dp(int x, int fa)
{
f[x][] = f[x][] = ;
if (size[x]==) return;
int mn = std::min(k, size[x]);
for (int i=head[x]; i!=-; i=nxt[i])
{
int v = edges[i].y, w=edges[i].val, upp = std::min(k, size[v]);
if (v!=fa){
dp(v, x);
for (int j=mn; j>=; j--)
for (int l=; l<=upp&&l<=j; l++)
{
ll left = 1ll*l*(k-l)*w;
ll right = 1ll*(size[v]-l)*(n-size[v]-k+l)*w;
f[x][j] = std::max(f[x][j], f[v][l]+f[x][j-l]+left+right);
}
}
}
}
int main()
{
memset(head, -, sizeof head);
n = read(), k = read();
if (k<< > n) k = n-k;
for (int i=; i<n; i++)
{
int u = read(), v = read(), w = read();
addedge(u, v, w);
addedge(v, u, w);
}
dfs(, );
for (int i=; i<=n; i++)
for (int j=; j<=size[i]; j++)
f[i][j] = -INF;
dp(, );
printf("%lld\n",f[][k]);
return ;
}

END

【树形背包】bzoj4033: [HAOI2015]树上染色的更多相关文章

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

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

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

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

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

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

  4. [bzoj4033][HAOI2015]树上染色_树形dp

    树上染色 bzoj-4033 HAOI-2015 题目大意:给定一棵n个点的树,让你在其中选出k个作为黑点,其余的是白点,收益为任意两个同色点之间距离的和.求最大收益. 注释:$1\le n\le 2 ...

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

    题目 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并 将其他的N-K个点染成白色.将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间 ...

  6. bzoj4033 [HAOI2015]树上染色——树形DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4033 树形DP,状态中加入 x 与父亲之间的边的贡献: 边权竟然是long long... ...

  7. 【题解】 bzoj4033: [HAOI2015]树上染色* (动态规划)

    bzoj4033,懒得复制,戳我戳我 Solution: 定义状态\(dp[i][j]\)表示\(i\)号节点为根节点的子树里面有\(j\)个黑色节点时最大的贡献值 然后我们要知道的就是子节点到根节点 ...

  8. 洛谷P3177||bzoj4033 [HAOI2015]树上染色

    洛谷P3177 bzoj4033 根本不会做... 上网查了题解,发现只要在状态定义的时候就考虑每一条边全局的贡献就好了? 考虑边的贡献和修改状态定义我都想到了,然而并不能想到要结合起来 ans[i] ...

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

    题目传送门 题目描述 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色.将所有点染色后,你会获得黑点两两之间的距离加 ...

随机推荐

  1. 【OpenJ_Bailian - 4110】圣诞老人的礼物-Santa Clau’s Gifts (贪心)

    圣诞老人的礼物-Santa Clau’s Gifts  Descriptions: 圣诞节来临了,在城市A中圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成 ...

  2. ADO学途 four day 数据库左右连接

    数据库的多表操作 数据库用于存放用户数据,用户数据库的数据又会有不同表来存放不同类型的数据,这这是就会产生多 张表来满足需求.列如,部门表有市场部,技术部,行政部等.,子表就有员工具体信息表用来存放员 ...

  3. C 语言实例 - 删除字符串中的特殊字符

    C 语言实例 - 删除字符串中的特殊字符 C 语言实例 C 语言实例 删除字符串中的除字母外的字符. 实例 #include<stdio.h> int main() { ]; int i, ...

  4. DB2 函数

    1.大小写转换 转大写UPPER 转小写LOWER

  5. pc端_移动端_H5_ QQ在线客服链接代码

    PC端:QQ在线客服链接代码 <a href="tencent://message/?uin=1239300678&Site=sc.chinaz.com&Menu=ye ...

  6. SpringMVC和Spring的配置文件扫描包详解

    在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景 ...

  7. uvm_base——打好你的基础

    uvm_base 是个很有意思的文件,这是UVM很巧妙的设计,将所有在base中包含的文件都包含在uvm_base.svh, 这样很方便管理各个文件直接的关系,而且还可以看出一些我之前没看过的东西,比 ...

  8. jmeter中通过命令方式生成结果文件

    通过命令的方式将jmeter生成的jtl结果文件生成html文件,以便更直观的分析结果数据,以下命令可以放在1个bat文件中取执行. bat文件可以放到jmeter的根目录下. 步骤1: 通过命令方式 ...

  9. sysdig安装和使用介绍

    安装步骤1)安装资源库rpm --import https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.publiccurl -s -o ...

  10. 线程池 Threadlocal 使用注意

    线程池中的线程是重复使用的,即一次使用完后,会被重新放回线程池,可被重新分配使用. 因此,ThreadLocal线程变量,如果保存的信息只是针对一次请求的,放回线程池之前需要清空这些Threadloc ...