HDU 5758 Explorer Bo 2016多校第三场1007 树上DP

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5758
题意:给你一颗树,树上有n个节点,有n-1条路,让你每条路至少走一遍,在走的过程中,你可以从一节点跳到其他任意节点,但不能重复走刚走过的路(也就是如果从a到b,那么此时不能立即从b走到a),让你找出在跳的次数最少的情况下走的最少步数。
题解:这是一个最小链覆盖问题。显然知道当走到叶子结点(也就是该节点没有子节点)时,这种情况是必须跳的。此时就应该跳到没有走到过的节点,但有由于需要考虑跳的次数要最少,那么我们知道在走如果以叶子结点为终点,此时跳的次数一定会增加(除非这是最后一次)。所以跳的时候一般从一个叶子结点跳到另外一个叶子结点。那么我们再来考虑走的步数最小化问题。根据以上分析,显然可以知道,为了首先保证跳的次数最少,那么每一次走一定是从一个叶子结点走到另外一个叶子结点,然后下一次又从另外一个没到过的叶子结点开始。因此,我们可以知道,对于一个节点的叶子结点来说,叶子结点个数的奇偶性决定了跳的次数。
点。
如图:
叶子结点为奇数时,首先从外面经过a节点后会走到b、c、d任意一个节点,那么剩下两个节点就可以从某一个开始走到另一个节点,比如从外面进来走到了b,那么下一步我们就可以从c走到d。
叶子结点为偶数时,同样进来先到某一个节点,那么剩下了奇数个叶子结点,肯定会多出一个向外走出去的(如果其他节点的叶子结点还没走完)。

根据以上分析,我们确定了跳的情况,那么就可以直接考虑一个暴力的树上dp求最小步数即可。dp[i]表示在深搜中i节点走到上一个节点的总步数(包括i节点子节点的步数)。由于要考虑叶子结点的两两搭配,那么我们可以增加一个维度来记录。详细看代码。
代码如下:

#include <bits/stdc++.h>
using namespace std;

const int inf = 0x3f3f3f3f;
const int maxn=1e5+50;
int t;
int n;
int dp[maxn][2];
//dp[i][0]表示i节点子节点各自要走的步数
//dp[i][1]表示i节点叶子结点两两搭配后的步数
int eadg[maxn],son[maxn];
vector<int>v[maxn];

void dfs(int x,int f)
{
	dp[x][0]=0;
	dp[x][1]=inf;
	int chd=0;
	//cout<<":"<<x<<":"<<endl;
	for(int i=0;i<v[x].size();i++)//先考虑x节点子节点各自走的步数
	{
		if(v[x][i]==f)
			continue;
		dfs(v[x][i],x);
		chd++;
		son[x]+=son[v[x][i]];
		int d=(son[v[x][i]]&1?1:2);//考虑x节点走到上一个节点步数,奇数+1,偶数+2
		dp[x][0]+=dp[v[x][i]][0]+d;
		//cout<<v[x][i]<<":"<<son[x]<<" "<<dp[x]<<endl;
	}
	for(int i=0;i<v[x].size();i++)//考虑x节点的叶子节点两两搭配之后的步数
	{
		if(v[x][i]==f)
			continue;
		if(son[v[x][i]]==1)
			dp[x][1]=min(dp[x][0],dp[x][1]);
		int d=(son[v[x][i]]&1?1:-1);
		dp[x][1]=min(dp[x][1],dp[x][0]-dp[v[x][i]][0]+dp[v[x][i]][1]+d);
	}
	if(!chd) son[x]=1;
}

int main()
{
	//freopen("in.txt","r",stdin);
	cin>>t;
	while(t--)
	{
		memset(son,0,sizeof(son));
		memset(eadg,0,sizeof(eadg));
		scanf("%d",&n);
		for(int i=0;i<=n;i++)
			v[i].clear();
		int a,b;
		for(int i=0;i<n-1;i++)
		{
			scanf("%d%d",&a,&b);
			v[a].push_back(b);
			v[b].push_back(a);
			eadg[a]++;
			eadg[b]++;
		}
		if(n==2)
		{
			puts("1");
			continue;
		}
		int root;
		int cnt=0;
		for(int i=1;i<=n;i++)
			if(eadg[i]!=1)
				root=i;
			else
				cnt++;
		dfs(root,0);
		printf("%d\n",dp[root][cnt&1]);
	}
	return 0;
}


原文地址:https://www.cnblogs.com/zhuyutian/p/5798910.html