【bzoj2466】[中山市选2009]树 树形dp

题目描述

图论中的树为一个无环的无向图。给定一棵树,每个节点有一盏指示灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。
开始的时候,所有的指示灯都是熄灭的。请编程计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。

输入

输入文件有多组数据。
输入第一行包含一个整数n,表示树的节点数目。每个节点的编号从1到n。 
输入接下来的n – 1行,每一行包含两个整数x,y,表示节点x和y之间有一条无向边。
当输入n为0时,表示输入结束。

输出

对于每组数据,输出最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。每一组数据独占一行。

样例输入

3
1 2
1 3
0

样例输出

1


题解

网上很多题解是高斯消元,然而我不会O(n^3)的算法,只会O(n)的树形dp(手动滑稽)

设a[x]表示x按且x亮的最小次数,b[x]表示x不按且x亮的最小次数,c[x]表示x按且x不亮的最小次数,d[x]表示x不按且x不亮的最小次数。

那么如果父亲按(状态a、c),则儿子必须不亮(状态c、d);如果父亲不按(状态b、d),则儿子必须亮(状态a、b)。

所以两个一组来转移,判断儿子的按与不按来更新。

状态转移方程有点复杂,自己慢慢体会~

注意最大值不能赋得过大,否则如果儿子全选择了最大值,那么会爆int。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 110
using namespace std;
int head[N] , to[N << 1] , next[N << 1] , cnt , a[N] , b[N] , c[N] , d[N];
void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
	int i , t;
	a[x] = 1 , d[x] = 0 , b[x] = c[x] = 100;
	for(i = head[x] ; i ; i = next[i])
	{
		if(to[i] != fa)
		{
			dfs(to[i] , x);
			t = a[x] , a[x] = min(a[x] + d[to[i]] , c[x] + c[to[i]]) , c[x] = min(c[x] + d[to[i]] , t + c[to[i]]);
			t = b[x] , b[x] = min(b[x] + b[to[i]] , d[x] + a[to[i]]) , d[x] = min(d[x] + b[to[i]] , t + a[to[i]]);
		}
	}
}
int main()
{
	int n , i , x , y;
	while(scanf("%d" , &n) != EOF && n)
	{
		memset(head , 0 , sizeof(head)) , cnt = 0;
		for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x);
		dfs(1 , 0);
		printf("%d\n" , min(a[1] , b[1]));
	}
	return 0;
}

 

 

posted @ 2017-06-20 15:43  GXZlegend  阅读(586)  评论(0编辑  收藏  举报