bzoj1956(一个快速简单的方法)

此题有树,那么能用的求最值的算法有dp,贪心,流等等,如果我们以dp的思路来解题,观察每颗子树,那么每颗子树都相当于曼哈顿路径的一部分链,子树根可以在链的头,尾,中间,其中根在头和尾是等价的。那么可以设计一个状态dp[i][0]表示,以i为根的树组成的一个从根i开始的链条,要花费的最小代价,dp[i][1]表示以i为根的树组成的一个从非根节点开始的链条,注意两个状态表示的链的出点都是非根节点。那么如果i是叶子节点,dp[i][0]=dp[i][1]=0;

现在考虑转移:我们现在的目标是把各个子节点代表的链条,和根节点,一起串成一个链。

如果根节点选择的子节点的状态为dp[i][0]那么不需要新加任何边,直接用树边就好了,如果选择的是dp[i][1]那么新加一条边。

我们现在需要讨论的就是到底子节点是选择用dp[i][0]好,还是选择用dp[i][1]好,显然如果我们对于每个子节点都能用dp[i][0]与dp[i][1]中的最小值,而且不加任何边是最好的,但是由于用了最小值,那么有可能就会新加一些边,这样就不一定好了

转移的时候就是首先找出根节点的各个子节点的min(dp[子节点][0],dp[子节点][1])的和,再考虑如何将所有这些链条串成一个。我们尽量考虑多用树边,但是这会和我们前面求和矛盾,因为如果我选了dp[子节点][1]这个链条进行组合,是不能用树边的不是?那么我前面的操作都是无用功了?当然不是。注意到,如果dp[子节点][1]<dp[子节点][0]那么dp[子节点][1]<=dp[子节点][0]-1,也就是说,我们就算多加一条边,最后得到的结果也是小于等于选择dp[字节点][0]来做链条的.有了这个性质就很好转移了

minn表示最小值的和,cnt表示选择的dp[子节点][0]的个数,size表示总的子节点数。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int first[150200], nextt[300400], too[300400];
int edgetot;
void addedge(int a, int b)
{
	too[edgetot] = b;
	nextt[edgetot] = first[a];
	first[a] = edgetot++;
	too[edgetot] = a;
	nextt[edgetot] = first[b];
	first[b] = edgetot++;
}
int n;
int dp[150200][2];
void com(int i, int &minn, int &cnt)
{
	if (dp[i][0] < dp[i][1])
	{
		minn += dp[i][0];
		cnt++;
	}
	else
	{
		if (dp[i][0] == dp[i][1])
		{
			minn += dp[i][0];
			cnt++;
		}
		else
			minn += dp[i][1];
	}
}
void dfs(int now, int fa)
{
	int cnt, minn, size;
	minn = 0; cnt = 0; size = 0;
	for (int i = first[now]; i != -1; i = nextt[i])
	{
		int to = too[i];
		if (to == fa)continue;
		dfs(to, now);
		com(to, minn, cnt);
		size++;
	}
	if (size == 0)
	{
		dp[now][0] = dp[now][1] = 0;
		return;
	}
	else
	{
		if (cnt >= 1)
		{
			dp[now][0] = min(dp[now][0], minn - 1+size);
		}
		else
		{
			dp[now][0] = min(dp[now][0], minn+size);
		}
		if (cnt >= 2)
		{
			dp[now][1] = min(dp[now][1], minn + size - 2);
		}
		else
		{
			if (cnt >= 1)
				dp[now][1] = min(dp[now][1], minn + size - 1);
			else
				dp[now][1] = min(dp[now][1], minn + size);
		}
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		first[i] = -1;
		dp[i][0] = dp[i][1] = 1000000000;
	}
	for (int i = 0; i < n - 1; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		addedge(a, b);
	}
	dfs(1, 1);
	printf("%d\n",min(dp[1][0] + 1,dp[1][1]+1));
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值