题意:
给一个n个节点的树加最少的边使得其成为一个环。
分析:
借鉴网上他人的图片分析
首先,我们来分析下对于一个点,我们有哪几种处理方法:一个点i,它在的环可能跟儿子没有关系,也可能跟儿子有关系,但是,可能跟两个儿子有关系,可能仅跟一个儿子有关系,但是不可能跟所有儿子(超过三个)有关系(那样就不符合仅在一个环中了)。 那么我们可以定义三个状态
dp(i,0)表示当前节点跟它的儿子们全部满足条件的情况下需要加的最少边,显然dp(1,0)就是答案。
dp(i,1)表示当前节点的儿子们全部满足条件的情况下需要加的最少边(即父亲不跟儿子一块成环)。
dp(i,2)表示当前节点跟一个儿子有关系的情况需要加的最少边。(为什么不是两个,因为两个儿子不一定有解,所以在dp(i,0)中对两个儿子进行更新,这里也是为更新dp(i,0)而服务计算的)
有四种状态转移(假设正在考虑的顶点是R,有k个儿子):
A.根R的所有子树自己解决(取状态0),转移到R的状态1。即R所有的儿子都变成每个顶点恰好在一个环中的图,R自己不变。
B.根R的k-1个棵树自己解决,剩下一棵子树取状态1和状态2的最小值,转移到R的状态2。剩下的那棵子树和根R就构成了长度至少为2的一条链。
C.根R的k-2棵子树自己解决,剩下两棵子树取状态1和状态2的最小值,在这两棵子树之间连一条边,转移到R的状态0。
D.根R的k-1棵子树自己解决,剩下一棵子树取状态2(子树里还剩下长度至少为2的一条链),在这棵子树和根之间连一条边,构成一个环,转移到R的状态0。
ACcode:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 10007
#define inf 0x3f3f3f3f
#include <cstring>
using namespace std;
struct N{
int to,next;
}my[maxn];
int head[maxn],tot;
int dp[maxn][3];
void add(int u,int v){
my[tot].to=v;
my[tot].next=head[u];
head[u]=tot++;
}
void init(){
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
tot=0;
}
void dfs(int st,int fa){
int sum=0,min1=inf,min2=inf,minver2=inf;
for(int i=head[st];i!=-1;i=my[i].next){
int now=my[i].to;
if(now!=fa){
dfs(now,st);
sum+=dp[now][0];
minver2=min(minver2,dp[now][2]-dp[now][0]);
if(min(dp[now][2]-dp[now][0],dp[now][1]-dp[now][0])<min1)
min2=min1,min1=min(dp[now][2]-dp[now][0],dp[now][1]-dp[now][0]);
else if(min(dp[now][2]-dp[now][0],dp[now][1]-dp[now][0])<min2)
min2=min(dp[now][2]-dp[now][0],dp[now][1]-dp[now][0]);
}
}
dp[st][1]=sum;
dp[st][2]=sum+min1;
dp[st][0]=sum+1+min(min1+min2,minver2);
}
int main(){
int n,x,y;
while(scanf("%d",&n)!=EOF){
init();
for(int i=1;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,-1);
printf("%d\n",dp[1][0]>=inf?-1:dp[1][0]);
}
return 0;
}