题目
题意: 给你1颗树。起点可以在n个点之间任意选择。每次走到头了,转方向或切换到另一个点都花费1个魔法。它想把n-1条道路都经过一遍,但是想花费魔法的次数最少。问最少情况下它走路最短是多少。
思路: 首先,一棵树的叶子节点的个数不管以谁为根都是一样的。假如这颗树的叶子节点个数是num。1.num是偶数 那么它使用的魔法次数最少就是num/2,叶子节点两两结合形成链。 2.num是奇数。使用的魔法次数最少就是(num+1)/2,其实就是其中num-1个叶子节点两两配对,其中一个直接连向根节点(花费为1)。
然后,x的儿子是y 假如y子树(包括y)里面呢叶子节点的个数是奇数,这个边的耗费就是1,因为配对好的叶子节点,她们之间的链只经过y,不经过x-y这条边。 假如是偶数,需要这些对里面其中有一对经过x-y这个边。(因为这个边要被覆盖。) 这就是第一个dfs的过程。多试几个不同的根dfs, 可以发现以谁为根都是一样的。
假如这棵树的叶子节点是偶数,那么dp[root]就是本题所要求的答案。
否则,这棵树需要枚举去除一个叶子节点的那个链,然后+1他的花费。
dfs2到叶子y结点的父亲x时候,先dfs2 y返回0 然后sz[y]=1 此时tmp=-1 但是return mx=0,这个时候代表是算上了那1个花费。 dfs2的撤销过程就是dfs的dp过程。
可是为什么对于任意一个点做根的答案一样呢!?我根本没试,感觉这题好懵逼啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
#include<cstdio>
#include<iostream>
#include<cstring>
#define m(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=1e5+5;
struct Edge{int to,nex;}edge[N<<1];int head[N],tot;
inline void add(int from,int to){
edge[++tot]=(Edge){to,head[from]},head[from]=tot;
edge[++tot]=(Edge){from,head[to]},head[to]=tot;
}
int sz[N],dp[N];
void dfs(int x,int fa){
int flag=0;
for(int i=head[x];i;i=edge[i].nex){
int y=edge[i].to;
if(y==fa) continue;
flag=1,dfs(y,x),sz[x]+=sz[y];
dp[x]+=dp[y]+((sz[y]&1)?1:2);
}
if(!flag) sz[x]=1;
}
int dfs2(int x,int fa){
int mx=0;
for(int i=head[x];i;i=edge[i].nex){
int y=edge[i].to;
if(y==fa) continue;
int tmp=dfs2(y,x);
tmp+=((sz[y]&1)?-1:1);
mx=max(mx,tmp);
}
return mx;
}
int main(){
int T;scanf("%d",&T);
while(T--){
m(dp,0),m(sz,0),m(head,0),tot=0;
int n;scanf("%d",&n);
if(n==1) {puts("0");continue;}
for(int i=1,x,y;i<n;++i)
scanf("%d%d",&x,&y),add(x,y);
if(n==2) {puts("1");continue;}
int root;
for(int i=1;i<=n;++i)
if(edge[head[i]].nex) {root=i;break;}
dfs(root,root);
if(!(sz[root]&1)) printf("%d\n",dp[root]);
else printf("%d\n",dp[root]-dfs2(root,root));
}
}