贪心的搜索也能做:http://blog.csdn.net/u013611908/article/details/44572549
转自:http://vainman.coding.io/
http://acm.fzu.edu.cn/problem.php?pid=2185
题意:給一棵树, 用最少的路径去覆盖所有的边, 求(1)允许边被重复覆盖, (2)不允许边被重复覆盖.
第一行是组数T(T <= 20). 每组两行, 第一行是n(1 <= n <= 10^5), 第二行是n – 1个数(0-based), 第i个数x[i]表示有一条边(x[i], i + 1), (0 <= i <= n – 2).
Sample Input
2
1
7
0 0 1 1 2 2
Sample Output
0 0
2 3
(预警:下面未必是对的,求指正)
先看第一问,一开始就猜可能是叶子数的一半,当时没证出来,看见别的题比较多人过就弃了这题。。。晚上才慢慢想到,,,,
首先,不可能用少于(L+1)/2条路径来覆盖所有的边,否则必然有不少于一个叶子的邻边没有被覆盖。
然后是(L+1)/2条边一定可以覆盖所有的边,可以用个类似于树的重心的特殊点来简略证明,这个点(设为C)是:
设整棵树的有L个叶子,这个点C满足以它的邻点为根的子树的叶子数都不超过L/2,那么每次只要从叶子最少的子树向最多叶子的子树连路径覆盖即可,这样,这些叶子之间的路径都经过这个特殊点C,对于任意一条边,都会被延伸向这条边下面的叶子结点的路径覆盖。
对于任意一棵树,一定可以找到这样的一个点:从任意一个点出发,设当前的点为v,最多只有一个子树的叶子数大于L/2(总共才L个叶子嘛。。),如果有这样一个子树T,那么把它的根置为v,重复下去,直到不存在叶子数大于L/2的子树,这时的当前点v就是我们要找的点了。
再看第二问,考虑叶子Vx的父亲结点F,显然最多有一个叶子能通过它的父亲F延伸出去,所以当F的子结点多于1个时,必然是两个两个地连路径覆盖,同时删除它们,剩下的就是一个子问题(如果F有奇数个子结点,最后剩下的点Vx到F到F的父亲是条链,压缩即可)。这样,对于一个点,它对答案的贡献是(它的度数 – 1)/2,但是到最后根结点是没有父亲的,这时算漏了一条边,最后要加上去(当然如果只有一个点是不用加的)。DFS一遍也是可以的,一开始就是这样子想的,后来一个邻校的朋友问用点的度数可不可以才发现这挺(jian)萌(dan)的好方法。。。
所以,答案就是(叶子个数+1)/2 和 sigma{ (度数-1)/2 } + (点数>1)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define rep(i, n) for(int i = 0; i < (n); ++i)
#define rep1(i, n) for(int i = 1; i <= (n); ++i)
const int N = 100010;
int a[N], n;
int main(){
int _, x;
scanf("%d", &_);
while(_--){
scanf("%d", &n);
memset(a, 0, sizeof(a[0]) * n);
rep(i, n - 1) scanf("%d", &x), ++a[i + 1], ++a[x];
int a1 = 0, a2 = 0;
rep(i, n) a1 += (a[i] == 1), a2 += (a[i] - 1) / 2;
printf("%d %d\n", (a1 + 1) / 2, a2 + (n > 1));
}
return 0;
}