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

  • 相关阅读:
    设计模式系列
    Python3 系列之 可变参数和关键字参数
    设计模式系列
    【HANA系列】SAP HANA ODBC error due to mismatch of version
    【FICO系列】SAP FICO FS00修改科目为未清项目管理
    【FIORI系列】SAP OpenUI5 (SAPUI5) js框架简单介绍
    【HANA系列】SAP HANA SQL获取当前日期加若干天后的日期
    【HANA系列】SAP HANA SQL获取本周的周一
    【HANA系列】SAP HANA SQL获取当前日期
    【HANA系列】SAP HANA SQL获取当前日期最后一天
  • 原文地址:https://www.cnblogs.com/snowy2002/p/10273649.html
Copyright © 2011-2022 走看看