[HDU5758] [2016多校联考3] Explorer Bo [树形dp]

[ L i n k \frak{Link} Link]


题意:给你一棵树。
随便找一个点出发,遍历整棵树。
不能走回头路,但是如果走到不能再走就可以传送到某个点。
要求最小化传送次数,并且在此前提下最小化走过的路径。


实际上相当于k笔画,传送次数就是奇数度数点数加一除以二。
也就是:叶子数加一除以二。

作为一道要遍历整棵树的题目,直觉来说要用树形dp
可是怎么用?
按照经典的思路,记F(pos)代表在某个点,覆盖其子树的最小代价。
对于这种路径权×路径经过次数的题目,一种常见的思路就是拆分,单独考虑边的贡献。
可以考虑x向上连的边、会被经过几次。

首先,因为要全部遍历,这个边至少要被经过一次。
什么情况下会经过多次呢?
既然链的端点是叶子,那么那一条进来的链的另一端就是子树外另一个叶子。
同时如果这样,那么假如子树里面的叶子个数是偶数的,现在就会变成奇数。
这个时候就需要另外一条边连出去。
然后最好就再也不要连出去了。也没有那个必要。
如果是奇数那就只会被走一遍啦。
同时我们也会发现:一个路径不是被走一次就是被走两次。
——只是这样吗?讨论就到此为止了?


要注意到一个细节。
假如,整棵树的叶子结点数量是奇数的——这个时候可没有“子树外”可以出去了。
这种情况需要特殊讨论。

这种情况下,会有一条路径被重复计算。不太好描述,自己画一个简单图模拟就明白了。
而这条路径是可以自己选哒

那么我们就可以考虑一下某个点是不是在路径上
用状态来表示,假如是的话会怎样怎样。然后消除这部分重复计算。
更加具体地,消除这条路径→叶子数奇偶性反转→路径被经过次数奇偶反转
找到一条能够减少最多代价的路径。


#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<cmath>
#include<ctime>
using namespace std;
#define add_edge(a,b) nxt[++tot]=head[a], head[a]=tot, to[tot]=b
int head[100005];
int nxt[200005];
int to[200005];
int t[200005];
int n, tot, root, ans, lv, sub;
int dfsA(int x, int fa) {
	int tt = 0;
	for (int i = head[x]; i; i = nxt[i]) {
		if (to[i] == fa) continue;
		t[i] = dfsA(to[i], x); //这条边被经过几次
		tt += t[i]; //累加被经过的次数
	}
	ans += tt; //更新不去重时的答案
	if (!tt) {
		++lv; //总的叶子数量
		return 1;
	}
	return (tt&1) ? 1 : 2;
}
int dfsB(int x, int fa) {
	int sub = 0;
	for (int i = head[x]; i; i = nxt[i]) {
		if (to[i] == fa) continue;
		sub = max(sub, dfsB(to[i], x) + ((t[i]==1) ? -1 : 1));
	}
	return sub;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		tot = ans = lv = 0; root = 1;
		memset(head, 0, sizeof(head));
		scanf("%d", &n);
		for (int x, y, i = 1; i < n; ++i) {
			scanf("%d%d", &x, &y);
			add_edge(x, y);
			add_edge(y, x);
		} if (n == 2) {
			printf("1\n");
			continue;
		} else if (n == 1) {
			printf("0\n");
			continue;
		} //一定要等输入完才能 continue; 
		while (!nxt[head[root]]) ++root;
		dfsA(root, 0);
		if (lv & 1) {
			printf("%d\n", ans - dfsB(root, 0));
		} else {
			printf("%d\n", ans);
		}
	}
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值