Luogu P3177 [HAOI2015] 树上染色(树上背包)
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu P3177 [HAOI2015] 树上染色(树上背包)相关的知识,希望对你有一定的参考价值。
整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
Luogu P3177 [HAOI2015] 树上染色
有一棵点数为 N N N 的树,树边有边权。给你一个在 0 ∼ N 0\\sim N 0∼N 之内的正整数 K K K ,你要在这棵树中选择 K K K 个点,将其染成黑色,并将其他 的 N − K N-K N−K 个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
0 ≤ n , k ≤ 2000 0≤n,k≤2000 0≤n,k≤2000
Solution
树上两点之间的距离即两点之间路径的长度,我们可以直接统计整棵树中每条边被计算了多少次,即对答案有多少贡献,既可求出最后的答案。
每条边被计算了几次,即被经过了几次。对于两个同色的结点,若他们在这条边的同侧,则该点对之间的路径不会经过该边。若他们在这条边的异侧,显然该点对之间的路径会经过该边一次。因此对于一条边 ( x , y ) (x, y) (x,y),被经过的次数 c n t cnt cnt,设 k k k 表示以 x x x 为根的子树中黑点的数量, m m m 表示需要选择的黑点总数量, s i z [ x ] siz[x] siz[x] 表示以 x x x 为根的子树的总点数,则有:
c n t = k × ( m − k ) + ( s i z [ x ] − k ) × ( n − m − ( s i z [ x ] − k ) ) \\mathrm {cnt} = k\\times(m - k) + (\\mathrm {siz}[x]-k)\\times (n-m-(\\mathrm {siz}[x]-k)) cnt=k×(m−k)+(siz[x]−k)×(n−m−(siz[x]−k))
由于这里并不满足最优子结构的性质,即当前子树的最大值并不一定会使得对于整棵树的答案是最大值,因此我们直接统计对整棵树的贡献,设 f [ i , j ] f[i, j] f[i,j] 表示在以 i i i 为根的子树中,选择 j j j 个黑点对整棵树的答案的贡献。
则有转移方程
f [ x , j ] = max { f [ x , j − k ] + f [ y , k ] + c n t × e d g e [ x , y ] } f[x,j] = \\max\\{f[x, j - k] + f[y, k] + \\mathrm {cnt}\\times \\mathrm {edge}[x, y]\\} f[x,j]=max{f[x,j−k]+f[y,k]+cnt×edge[x,y]}
然后就是一个树上 0/1 背包了,由于每个点只能选择一次,所以倒序枚举 j j j 。然后我们只需要考虑对于以 x x x 的子结点 y y y 为根的子树,选或者不选。
-
若以 x x x 的子结点 y y y 为根的子树选择点将其染成黑色,通过转移方程计算即可。
-
若以 x x x 的子结点 y y y 为根的子树不选择点将其染成黑色,则该子树对答案的贡献为子树纯白的时候对答案的贡献,计算出后,与选择以 x x x 的子结点 y y y 为根的子树中的点的情况得到的答案取 max \\max max 即可。
选或不选两种情况,所以我们的 k k k 应该枚举 0 ∼ s i z [ y ] 0\\sim \\mathrm {siz}[y] 0∼siz[y]。至于如何,正序倒序枚举均可。
这里有一个细节,我们的原转移方程是
f
[
x
,
j
]
=
max
{
f
[
x
,
j
−
k
]
+
f
[
y
,
k
]
+
c
n
t
×
e
d
g
e
[
x
,
y
]
}
f[x,j] = \\max\\{f[x, j - k] + f[y, k] + \\mathrm {cnt}\\times \\mathrm {edge}[x, y]\\}
f[x,j]=max{f[x,j−k]+f[y,k]+cnt×edge[x,y]}
由于 n ≤ 2000 n\\le 2000 n≤2000,直接转移时间复杂度是 O ( K N 2 ) O(KN^2) O(KN2),最后一个点跑了 1.2s TLE了。
我们可以将转移方程改为
f [ x , j + k ] = max { f [ x , j ] + f [ y , k ] + c n t × e d g e [ x , y ] } f[x,j+k] = \\max\\{f[x, j] + f[y, k] + \\mathrm {cnt}\\times \\mathrm {edge}[x, y]\\} f[x,j+k]=max{f[x,j]+f[y,k]+cnt×edge[x,y]}
先进行转移,然后再计算 siz[x] += siz[y]
,即从小到大递推,并保证
j
+
k
≤
m
j+k\\le m
j+k≤m,时间复杂度大概是
O
(
N
2
)
O(N^2)
O(N2)…不会证,最后一个点从 1.2s 优化到了 34ms…
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 2e3 + 7, maxm = maxn * 2;
int n, m, s, t, ans;
ll f[maxn][maxn];
int num_white;
int siz[maxn];
int head[maxn], ver[maxm], edge[maxm], nex[maxm], tot;
void add(int x, int y, int z)
{
ver[tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot ++ ;
}
void init()
{
memset(head, -1, sizeof head);
}
void dfs(int x, int fa)
{
siz[x] = 1;
ll g[maxn];
memset(g, 0, sizeof g);
for (int i = head[x]; ~i; i = nex[i]) {
int y = ver[i], z = edge[i];
if(y == fa) continue;
dfs(y, x);
for (int j = min(m, siz[x]); j >= 0; -- j) {
for (int k = min(m, siz[y]); k >= 0; -- k) {
if(j + k > m) continue;
g[j + k] = max(g[j + k], f[x][j] + f[y][k] + 1ll * z * (k * (m - k) + (siz[y] - k) * ((n - m) - (siz[y] - k))));
}
}
siz[x] += siz[y];
memcpy(f[x], g, sizeof g);
}
}
int main()
{
init();
scanf("%d%d", &n, &m);
for (int i = 1; i <= n - 1; ++ i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
add(x, y, z);
add(y, x, z);
}
dfs(1, 0);
cout << f[1][m] << endl;
return 0;
}
以上是关于Luogu P3177 [HAOI2015] 树上染色(树上背包)的主要内容,如果未能解决你的问题,请参考以下文章