【中山市选2009】树

【中山市选2009】树

描述

Description

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

Input

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

Output

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

Sample Input

3
1 2
1 3
0

Sample Output

1

Hint

【数据规模】
  对于20%的数据,满足1 <= n <=15。
  对于40%的数据,满足1 <= n <=50。
  对于100%的数据,满足1 <= n <=100。


做题过程

这是一棵树,所以一眼看下去,马上想到树形DP。
我推了一个方程,打了出来。后来读题时感到不对劲,发现我将那些操作看成覆盖了。
我又推了一遍方程,于是AC了。

分析

这就是一道很水的树形DP。
fifa 表示i被父亲点亮时按的最少按钮
fiso 表示i被儿子点亮时按的最少按钮
fise 表示i被自己点亮时按的最少按钮
j i的儿子
对于 fifa ,它的儿子不能将它点亮,所以有偶数个儿子点亮了自己

fifa=minfjse+fjso

对于 fiso ,它的儿子要将它点亮,所以 有奇数个儿子点亮了自己
fiso=minfjse+fjso

对于 fise ,它点亮了儿子,所以 它的儿子都应被它点亮
fise=1+fjfa

这就是状态转移方程了。
初始化:
fifa=fiso=fise=1

但是,用普通方法求 fifa fiso 是很慢的。
不妨转换一下式子
fifa=minfjso+fjsefjsofiso=minfjso+fjsefjso

显然 左边是固定的,我们要让 右边的值最小
很简单的一个想法就是排序,然后按偶或奇个数选出最小值就行了。
FQY大佬处理这个东西时用DP,厉害了!
答案为 minfrootso,frootse


代码

#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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值