Luogu P3177 [HAOI2015] 树上染色(树上背包)

Posted 繁凡さん

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu P3177 [HAOI2015] 树上染色(树上背包)相关的知识,希望对你有一定的参考价值。

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


Luogu P3177 [HAOI2015] 树上染色

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

0 ≤ n , k ≤ 2000 0≤n,k≤2000 0n,k2000

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×(mk)+(siz[x]k)×(nm(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,jk]+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] 0siz[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,jk]+f[y,k]+cnt×edge[x,y]}

由于 n ≤ 2000 n\\le 2000 n2000,直接转移时间复杂度是 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+km,时间复杂度大概是 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] 树上染色(树上背包)的主要内容,如果未能解决你的问题,请参考以下文章

P3177 [HAOI2015]树上染色(树形DP)

p3177 [HAOI2015]树上染色

P3177 [HAOI2015]树上染色

luogu3178 [HAOI2015]树上操作

「Luogu P3178」[HAOI2015]树上操作

luogu P3178 [HAOI2015]树上操作 题解