题目描述
图论中的树为一个无环的无向图。给定一棵树,每个节点有一盏指示灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。
开始的时候,所有的指示灯都是熄灭的。请编程计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。
输入
输入文件有多组数据。
输入第一行包含一个整数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 " , min(a[1] , b[1])); } return 0; }