• 【中山市选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被自己点亮时按的最少按钮
    ji的儿子
    对于fifa,它的儿子不能将它点亮,所以有偶数个儿子点亮了自己

    fifa=minfjse+fjso

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

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

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

    但是,用普通方法求fifafiso是很慢的。
    不妨转换一下式子
    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
    ",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;
        }
    }
  • 相关阅读:
    Kruskal重构树学习笔记
    亚洲和欧洲的分界线是谁划分的?
    代码目录 (App_Code 目录)及namespace的理解
    Events解惑——来自MSDN
    HttpContext.Current.Response和Response有什么区别?
    Ramdisk 内存盘的使用
    MVC模式 介绍
    关于Windows Workflow Foundation 调试时的经验小解(不断添加)
    关于类成员变量的声明和实例化的时机
    软件名称备份
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145296.html
Copyright © 2020-2023  润新知