【中山市选2009】树
【中山市选2009】树
描述
Description
图论中的树为一个无环的无向图。给定一棵树,每个节点有一盏指示灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。
开始的时候,所有的指示灯都是熄灭的。请编程计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。Input
输入文件有多组数据。
输入第一行包含一个整数n,表示树的节点数目。每个节点的编号从1到n。
输入接下来的n – 1行,每一行包含两个整数x,y,表示节点x和y之间有一条无向边。
当输入n为0时,表示输入结束。Output
对于每组数据,输出最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。每一组数据独占一行。
Sample Input
3
1 2
1 3
0Sample Output
1
Hint
【数据规模】
对于20%的数据,满足1 <= n <=15。
对于40%的数据,满足1 <= n <=50。
对于100%的数据,满足1 <= n <=100。
做题过程
这是一棵树,所以一眼看下去,马上想到树形DP。
我推了一个方程,打了出来。后来读题时感到不对劲,发现我将那些操作看成覆盖了。
我又推了一遍方程,于是AC了。
分析
这就是一道很水的树形DP。
设
设
对于
对于
对于
这就是状态转移方程了。
初始化:
但是,用普通方法求
不妨转换一下式子
显然左边是固定的,我们要让右边的值最小。
很简单的一个想法就是排序,然后按偶或奇个数选出最小值就行了。
答案为
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
struct EDGE
{
int to;
EDGE* las;
} e[201];
EDGE *last[101];
struct Status
{
long long fa,so,se;//father,son,self
} f[101];
int a[101][107];
int na[101];
bool cmp(int x,int y)
{
return f[x].se-f[x].so<f[y].se-f[y].so;
}
void dp(int,int);
int main()
{
int i,j,x,y;
for (scanf("%d",&n);n;scanf("%d",&n))
{
memset(last,0,sizeof last);
j=0;
for (i=1;i<n;++i)
{
scanf("%d %d",&x,&y);
last[x]=&(e[++j]={y,last[x]});
last[y]=&(e[++j]={x,last[y]});
}
memset(na,0,sizeof na);
dp(1,0);
printf("%lld\n",min(f[1].so,f[1].se));
}
return 0;
}
void dp(int x,int fa)
{
f[x].se=1;
long long sum_so=0;
EDGE *ei;
for (ei=last[x];ei;ei=ei->las)
#define son ei->to
if (ei->to!=fa)
{
dp(son,x);
a[x][++na[x]]=ei->to;//记录儿子,方便排序
sum_so+=f[son].so;//记录总和
f[x].se+=f[son].fa;//转移f[x].se
}
sort(a[x]+1,a[x]+na[x]+1,cmp);//排序
int i;
long long sum=0;
f[x].fa=0x7f7f7f7f;
for (i=0;i<=na[x];i+=2)//转移f[x].fa
{
f[x].fa=min(f[x].fa,sum_so+sum);
sum+=f[a[x][i+1]].se-f[a[x][i+1]].so+f[a[x][i+2]].se-f[a[x][i+2]].so;
}
sum=f[a[x][1]].se-f[a[x][1]].so;
f[x].so=0x7f7f7f7f;
for (i=1;i<=na[x];i+=2)//转移f[x].so
{
f[x].so=min(f[x].so,sum_so+sum);
sum+=f[a[x][i+1]].se-f[a[x][i+1]].so+f[a[x][i+2]].se-f[a[x][i+2]].so;
}
}