P1272 重建道路(树形背包)

题目描述

一场可怕的地震后,人们用 N N N 个牲口棚(编号 1 ∼ N 1 \sim N 1N)重建了农夫 John 的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。

John 想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有 P 个牲口棚的子树和剩余的牲口棚分离,John 想知道这些道路的最小数目。
P1272 重建道路(树形背包)

题意分析

题目的意思是给定一棵具有根节点的树,节点个数一共有N个节点。现在问最少断多少条边,可以使得某个子树的节点个数恰好为P

其实这道题的难点在于,为什么是用分组背包来解,以及怎么实现这个分组背包?如果将两个知识点拆开来看,分组背包,树形DP都好理解。但如果将两个知识点合并,就很容易考察出是否真正地理解,树形背包就是一个很好的例子。

分组背包:在选择物品的时候,一开始将物品分为好几组,在选择时,可以从每一组中至多选择一件物品,问如何获得最大的价值

说是分组背包的一个问题,其实真正对比上去,还是有较大的差异,分组背包只能帮助理解,不能直接代入到树形背包问题(我的一点认识)

树形背包

先用每个点的出度,初始化状态,也就是边的数量,可能要拆掉的边

  • 状态表示f[i][j]:以i为根的子树,保留j个点,需要拆掉的边的数量的方案(取最小)
  • 状态计算: f [ n o w ] [ k ] = m i n ( f [ n o w ] [ k ] , f [ n o w ] [ k − s ] + f [ s o n ] [ s ] − 1 ) f[now][k]=min(f[now][k],f[now][k-s]+f[son][s]-1) f[now][k]=min(f[now][k],f[now][ks]+f[son][s]1)

这里为什么要-1呢?我们拿题目的样例举例子:f[1][1]=4代表以1为根节点,保留1个点所要拆除的边的数量为4,f[5][1]=0表示需要拆除1条边(这里的边仅考虑往下的情况,往上的最后会统一处理)。1和5节点具有父子关系,所以可以进行上述的状态计算,也就是状态转移。 f [ 1 ] [ 2 ] = f [ 1 ] [ 1 ] + f [ 5 ] [ 1 ] − 1 = 4 + 0 − 1 = 3 f[1][2]=f[1][1]+f[5][1]-1=4+0-1=3 f[1][2]=f[1][1]+f[5][1]1=4+01=3,因为两个点合并了,它们之间的边得以保留所以要-1。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 160;
int n, p;
int h[N], e[N], ne[N], idx;
int a[N], b[N]; // 出度、入度 
int f[N][N]; // 表示以i为根的子树,保留j个点(包括自身)所要删除的边数 

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u) {
	
	int temp = 0, sum = 1;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		
		temp = dfs(j), sum += temp;
		 // 分组背包(难点)
		for (int k = sum; k >= 1; k--) {
			for (int s = 1; s < k; s++) {
				f[u][k] = min(f[u][k], f[u][k-s] + f[j][s] - 1);
			}
		}
	}
	return sum;
}

int main() {
	cin >> n >> p;
	memset(h, -1, sizeof h);
	memset(f, 0x3f, sizeof f);
	for (int i = 0; i < n - 1; i++) {
		int x, y;
		cin >> x >> y;
		add(x, y);
		a[x]++, b[y] = 1;
	}
	
	int root = 0;
	for (int i = 1; i <= n; i++) {
		if (!b[i]) root = i;
		f[i][1] = a[i];
	}
	dfs(root);
	int ans = f[root][p];
	// 这里的+1表示的就是因为这里主要针对的是除根节点以外的点
	// 它们如果是答案的话,要多减去与父节点相关联的边
	for (int i = 1; i <= n; i++) ans = min(ans, f[i][p] + 1);
	
	cout << ans << endl;
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeSlogan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值