zoukankan      html  css  js  c++  java
  • 再谈树形dp

    上次说了说树形dp的入门

    那么这次该来一点有难度的题目了:

    UVA10859 Placing Lampposts

    给定一个n个点m条边的无向无环图,在尽量少的节点上放灯,使得所有边都与灯相邻(被灯照亮)。

    在灯的总数最小的前提下,被两盏灯同时照亮的边数应该尽可能大。

    输入格式

    第一行输入T,为数据组数。

    每组数据第一行输入n,m,分别为该组数据中图的点数和边数。

    以下m行,输入各边的两端点u,v

    输出格式

    输出共T行。

    对每组数据,一行输出三个数,最小灯数、被两盏灯同时照亮的边数、只被一盏灯照亮的边数。

    n<=1000

    有向无环图说白了就是一个森林(可以自己画图看看),第一问这不就是裸的树形dp求最大独立集吗?在每个森林上跑一遍树形dp就行。不过第二问第三问倒有点意思,怎么维护两边都放灯的道路的数量呢?这里介绍一个十分巧妙的方法,由于n<=1000,我们就可以把一个节点的权值设为比1000大的数,然后在转移的时候,如果这条路的两端节点没有都选,那么就+1,代表有多少只被一盏灯照亮的路,最后的答案除以k就是第一问,mod k就是第三问,用m减第三问的答案就是第二问。

    void dfs(int x)
    {
        dp[x][1]=k;//这里的k我们设为大于1000的数
        dp[x][0]=0;
        d[x]=1;
        for(int i=last[x];i;i=g[i].next)
        {
            int v=g[i].to;
            if(d[v]) continue;
            dfs(v);
            dp[x][1]+=min(dp[v][0]+1,dp[v][1]);
            dp[x][0]+=dp[v][1]+1;//如果只被一盏灯照亮就加上1,目的是和被两盏灯同时照亮的边区分,同时也保证了被两盏灯同时照亮的边数应该尽可能大,毕竟我们取最小值。
        } 
    }

    经过这样一番神奇的操作,我们就成功的切掉了这道看似有点神仙的题目。

    说了这么多,怎么能没有代码呢?

    #include<iostream>
    #include<cstdio>
    #include<string>
    #include<cmath>
    #include<cstring>
    #include<queue>
    #include<stack>
    #include<algorithm>
    #define maxn 3005
    using namespace std;
    
    struct edge
    {
        int next;
        int to;
    }g[maxn];
    
    inline int read()
    {
        char c=getchar();
        int res=0,x=1;
        while(c<'0'||c>'9')
        {
            if(c=='-')
            x=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            res=res*10+(c-'0');
            c=getchar();
        }
        return x*res;
    }
    
    int t,n,m,num,aa,bb,ans;
    int k=3000;
    int last[maxn],dp[maxn][2],d[maxn];
    
    inline void add(int from,int to)
    {
        g[++num].next=last[from];
        g[num].to=to;
        last[from]=num;
    }
    
    void dfs(int x)
    {
        dp[x][1]=k;
        dp[x][0]=0;
        d[x]=1;
        for(int i=last[x];i;i=g[i].next)
        {
            int v=g[i].to;
            if(d[v]) continue;
            dfs(v);
            dp[x][1]+=min(dp[v][0]+1,dp[v][1]);
            dp[x][0]+=dp[v][1]+1;
        }
    }
    
    int main()
    {
        t=read();
        while(t--)
        {
            n=read();m=read();
            num=0;ans=0;
            memset(last,0,sizeof(last));
            memset(dp,0,sizeof(dp));
            memset(d,0,sizeof(d));
            for(int i=1;i<=m;i++)
            {
                aa=read();bb=read();
                add(aa,bb);
                add(bb,aa);
            }
            for(int i=1;i<=n;i++)
            {
                if(!d[i])
                {
                    dfs(i);
                    ans+=min(dp[i][1],dp[i][0]);
                }
            }
            printf("%d %d %d
    ",ans/k,m-(ans%k),ans%k);
        }
    }
    View Code

    下面再来看这样的一道简(shen)单(xian)题

    UVA1220 Hali-Bula的晚会 Party at Hali-Bula

    公司里有n(n<=200)个人形成一个树状结构,即除了老板之外每个员工都有唯一的直属上司。要求选尽量多的人,但不能同时选择一个人和他的直属上司。问:最多能选多少人,以及在人数最多的前提下方案是否唯一。

    输入:第一行一个数n;第二行输入老板的名字;以下的n-1行中,每行是一位员工的名字和其直属上司的名字(英文单词,长度为1到100),两个名字之间有空格隔开,'0'为输入结束的标识符。

    输出:一行,输出一个数字,表示最大的访客数量。并再同一行输出单词'Yes'或'No',代表目前方案是否唯一。

    这个的第一问好像有点简单的样子,但是这第二问好像有点毒瘤啊。我们不妨从状态转移上入手,

    dp[x][1]+=dp[v][0];
    dp[x][0]+=max(dp[v][1],dp[v][0]);

    不难发现,如果 dp[v][1]==dp[v][0] 那么不就会出现两种方式了吗,因此我们用c数组来维护一下方案书是否唯一就行了,我们先判断孩子的方案数是否唯一,再用孩子去更新父亲,因为如果孩子的方案数不唯一,那么由这个孩子转移后的父亲肯定方案数也不唯一,这样就可以愉快的树形dp了。

    像这样:

        if(dp[v][0]>dp[v][1]&&c[v][0])
        {
            c[x][0]=1;
        }
        if(dp[v][1]>dp[v][0]&&c[v][1])
        {
            c[x][0]=1;
        }
        if(dp[v][1]==dp[v][0])
        {
            c[x][0]=1;
        }
        if(c[v][0])
        {
            c[x][1]=1;
        }

    最后怎么少得了完整ac代码呢?

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<string>
      4 #include<cmath>
      5 #include<cstring>
      6 #include<queue>
      7 #include<stack>
      8 #include<algorithm>
      9 #include<map>
     10 #define maxn 2005
     11 using namespace std;
     12 
     13 struct edge
     14 {
     15     int next;
     16     int to;
     17 }g[maxn];
     18 
     19 inline int read()
     20 {
     21     char c=getchar();
     22     int res=0,x=1;
     23     while(c<'0'||c>'9')
     24     {
     25         if(c=='-')
     26         x=-1;
     27         c=getchar();
     28     }
     29     while(c>='0'&&c<='9')
     30     {
     31         res=res*10+(c-'0');
     32         c=getchar();
     33     }
     34     return x*res;
     35 }
     36 
     37 int n;
     38 string aa,bb,root;
     39 int cnt;
     40 int num;
     41 int last[maxn],dp[maxn][2],d[maxn],c[maxn][2];
     42 map<string,int>a;
     43 
     44 inline void add(int from,int to)
     45 {
     46     g[++num].next=last[from];
     47     g[num].to=to;
     48     last[from]=num;
     49 }
     50 
     51 void dfs(int x)
     52 {
     53     d[x]=1;
     54     dp[x][1]=1;
     55     dp[x][0]=0;
     56     for(int i=last[x];i;i=g[i].next)
     57     {
     58         int v=g[i].to;
     59         if(!d[v])
     60         {
     61             dfs(v);
     62             dp[x][1]+=dp[v][0];
     63             dp[x][0]+=max(dp[v][1],dp[v][0]);
     64             if(dp[v][0]>dp[v][1]&&c[v][0])
     65             {
     66                 c[x][0]=1;
     67             }
     68             if(dp[v][1]>dp[v][0]&&c[v][1])
     69             {
     70                 c[x][0]=1;
     71             }
     72             if(dp[v][1]==dp[v][0])
     73             {
     74                 c[x][0]=1;
     75             }
     76             if(c[v][0])
     77             {
     78                 c[x][1]=1;
     79             }
     80         }
     81     }
     82 }
     83 
     84 int main()
     85 {
     86     while(1)
     87     {
     88         n=read();
     89         if(n==0) break;
     90         cnt=0;num=0;
     91         memset(last,0,sizeof(last));
     92         memset(dp,0,sizeof(dp));
     93         memset(d,0,sizeof(d));
     94         memset(c,0,sizeof(c));
     95         a.clear();
     96         for(int i=1;i<=n;i++)
     97         {
     98             if(i==1) 
     99             {
    100                 cin>>root;
    101                 a[root]=++cnt;
    102             }
    103             else
    104             {
    105                 cin>>aa>>bb;
    106                 if(!a[aa])
    107                 {
    108                     a[aa]=++cnt;
    109                 }
    110                 if(!a[bb])
    111                 {
    112                     a[bb]=++cnt;
    113                 }
    114                 add(a[aa],a[bb]);
    115                 add(a[bb],a[aa]);
    116             }
    117         }
    118         dfs(1);
    119         printf("%d ",max(dp[1][1],dp[1][0]));
    120         if(dp[1][0]==dp[1][1]||(dp[1][0]<dp[1][1]&&c[1][1])||(dp[1][0]>dp[1][1]&&c[1][0]))
    121         printf("No
    ");
    122         else printf("Yes
    ");
    123     }    
    124 }
    View Code

    No man or woman is worth your tears, and the one who is, won't make you cry.

                                                                                                                                            --snowy

                                                                                                                                  2019-01-15    18:48:21

  • 相关阅读:
    mysql授权GRANT ALL PRIVILEGES
    MySQL修改root密码的多种方法
    javaagent
    JavaAgent 应用(spring-loaded 热部署)
    JavaAgent入门
    java运行jar命令提示没有主清单属性
    连接到 redis 服务
    PHP中的socket TCP编程
    Memcached 与 Redis 区别
    rc.local配置
  • 原文地址:https://www.cnblogs.com/snowy2002/p/10273649.html
Copyright © 2011-2022 走看看