题意:给你一棵树。
随便找一个点出发,遍历整棵树。
不能走回头路,但是如果走到不能再走就可以传送到某个点。
要求最小化传送次数,并且在此前提下最小化走过的路径。
实际上相当于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;
}