紫书 例题 9-14 UVa 1218 (树形dp)

这道题有个初始值设成1e9, 然后这个值是要加很多次的,然后就会溢出变成负数,
然后就一直WA, 找这个bug找了一个小时……以后不能随便这样设那么大,
要考虑会不会加很多次然后溢出。

讲一下思路。
首先对于当前节点u,可以分三种情况。
一种是当前节点是服务器,一种是节点的父亲是服务器,一种是节点的儿子是服务器。
不可能都不是服务器,因为如果这样的话u自己就没有服务器相连。
设本身是服务器的为d(u, 0),父亲是服务器为d(u, 1),儿子是服务器是d(u, 2)
那么我们可以写出转移方程
d(u, 0):此时u的儿子可以是服务器也可以不是服务器,所以d[u][0] = sum{min(d(v, 0),d(v, 1)) | v是u的儿子} + 1(本身)
d(u, 1):此时u的儿子全部都不是服务器,因为父亲已经是,只能和一个服务器相连。,所以d(u, 1) = sum{d(v, 2)| v是u的儿子}
d(u, 2):此时u的节点中一定有且只有一个儿子有服务器,所以我们可以枚举这个儿子是哪一个,设这个儿子是q, 其他的所有儿子是p, 那么d(u, 2) = min(d(q, 0) + sum(d(p, 2)))
但是这样非常的耗时间,我们可以利用前面的式子代换一下。
我们观察 sum(d(p, 2)), p是除了q以外的所有儿子,所以可以用所有儿子的值减去q的值,
也就是sum(d(p, 2)) = sum(d(v, 2)) - d(q, 2) = d(u, 1) - d(q, 2)。所以枚举q取最小的就好了。
以0为根节点,最后输出min(d(0, 0), d(0, 2)), 根节点没有父节点,所以d(0, 1)不理他
 

#include<cstdio>
#include<vector>
#include<algorithm>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;

const int MAXN = 11234;
int d[MAXN][3], n;
vector<int> g[MAXN];

void dfs(int u, int fa)
{
	d[u][1] = 0; d[u][0] = 1;
	if(g[u].size() == 1 && fa != -1) return;
	
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(v == fa) continue;
		dfs(v, u);
		d[u][1] += d[v][2];
		d[u][0] += min(d[v][1], d[v][0]);
	}
	
	REP(i, 0, g[u].size())
	{
		int v = g[u][i];
		if(v == fa) continue;
		d[u][2] = min(d[u][2], d[u][1] - d[v][2] + d[v][0]);
	}
}

int main()
{
	while(~scanf("%d", &n))
	{
		REP(i, 0, n) g[i].clear(), d[i][2] = MAXN + 1;
		REP(i, 0, n - 1)
		{
			int a, b;
			scanf("%d%d", &a, &b);
			a--; b--;
			g[a].push_back(b);
			g[b].push_back(a);
		}
		
		dfs(0, -1);
		printf("%d\n", min(d[0][0], d[0][2]));
		scanf("%d", &n);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34416123/article/details/81513680