zoukankan      html  css  js  c++  java
  • ACM之路(13)—— 树型dp

      最近刷了一套(5题)的树型dp题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=116767#overview,算是入了个门,做下总结。

      A题是真正的入门题,只要找出所有的入度为0的点(即每棵树上最高的领导)进行树型dp即可,具体方法同B题,等到了B题再具体讲。

      C题是给出一棵树,升序输出这棵树上所有满足条件的点,该条件是,去掉该节点以后剩下的所有子树的size不超过原数size的一半。方法是以节点1为头,预处理出所有节点的size,然后用dfs遍历所有的点,找出符合条件的点即可。

      D题是,给出一个树(树上每个节点都有一个权值)和一个指定的size,找出满足这个size的所有子树中权值最大的一颗子树的权值。因为节点数最大为100,所以只要暴力dp即可。

      虽然CD两题都不是很难,但还是需要注意一下写法的。

      重点讲下B和E两题。题意都差不多,给出一棵树,B题是每个点都可以覆盖其相邻的一条边,求覆盖所有边所需最少的点的个数。而E题是,每个点都可以覆盖自己和相邻的节点,求覆盖所有点所需的最少的点的个数。

      一开始并没有觉得有什么区别,因为都是无环的树,应该B题的方法适用于E,不过最后还是找出了反例。

      如上图,如果是覆盖所有点,那么只要2,5两个点即可,如果是覆盖边,那么3,4之间的边仍未覆盖,所以有不同,需要采取新的dp方法。

      对于B题,考虑如果u点不设置点,那么即dp[u][0]=dp[v][1],即其子节点一定要放一个点(1表示放,0表示不放);否则,如果u点放置,则dp[u][1]=min(dp[v][0],dp[v][1])。然后用dfs遍历即可。代码如下:

     1 #include <stdio.h>
     2 #include <algorithm>
     3 #include <string.h>
     4 #include <vector>
     5 using namespace std;
     6 const int N = 1500;
     7 int dp[N+5][2];
     8 vector<int> vec[N+5];
     9 void dfs(int u,int f)
    10 {
    11     dp[u][0]=0;
    12     dp[u][1]=1;
    13     for(int i=0;i<vec[u].size();i++)
    14     {
    15         int v=vec[u][i];
    16         if(f==v) continue;
    17         dfs(v,u);
    18         dp[u][0]+=dp[v][1];
    19         dp[u][1]+=min(dp[v][1],dp[v][0]);
    20     }
    21 }
    22 int main()
    23 {
    24     int n;
    25     while(scanf("%d",&n)==1)
    26     {
    27         memset(dp,0,sizeof(dp));
    28         for(int i=0;i<=N;i++) vec[i].clear();
    29         for(int i=1;i<=n;i++)
    30         {
    31             int u,T;
    32             scanf("%d:(%d)",&u,&T);
    33             while(T--)
    34             {
    35                 int v;
    36                 scanf("%d",&v);
    37                 vec[u].push_back(v);
    38                 vec[v].push_back(u);
    39             }
    40         }
    41         dfs(0,-1);
    42         int ans = min(dp[0][0],dp[0][1]);
    43         printf("%d
    ",ans);
    44     }
    45     return 0;
    46 }

      

      对于D题,贪心策略是如果子节点建了,父节点不建;父节点建了,子节点不建。具体的还是见代码理解一下吧:

     1 #include <stdio.h>
     2 #include <algorithm>
     3 #include <string.h>
     4 #include <vector>
     5 using namespace std;
     6 
     7 vector<int> G[10000+5];
     8 int build[100000+5];
     9 int ans = 0;
    10 void dfs(int u,int from)
    11 {
    12     int f = 0; //f=1表示所有子代中具有一个已经放置了点
    13     for(int i=0;i<G[u].size();i++)
    14     {
    15         int &v = G[u][i];
    16         if(v == from) continue;
    17         dfs(v,u);
    18         f = f||build[v];
    19     }
    20     if(from == -1) ans += (f==0&&build[u]==0);
    21     else if(build[from] == 0 && build[u] == 0 && f == 0)
    22     {
    23         ans++;
    24         build[from] = 1; //这是贪心策略的体现,可以手动画图模拟一下
    25     }
    26 }
    27 int main()
    28 {
    29     int n;
    30     while(scanf("%d",&n)==1)
    31     {
    32         for(int i=1;i<=n;i++) G[i].clear();
    33         memset(build,0,sizeof(build));
    34         for(int i=1;i<n;i++)
    35         {
    36             int u,v;
    37             scanf("%d%d",&u,&v);
    38             G[u].push_back(v);
    39             G[v].push_back(u);
    40         }
    41         ans = 0;
    42         dfs(1,-1);
    43         printf("%d
    ",ans);
    44     }
    45 }

     模拟过程大致如下:

     

    由于贪心策略,最后三个节点不涂,倒数第二个涂色,那么3自然不用涂,回溯到2节点的时候,由于1,2,3都没涂,而3是有三个儿子节点可以覆盖它的,故不用管,所以涂1即可,这就是代码中,连续3个没涂的情况下涂最前面那个(也就是from)的缘故。同时这也是和B题不同的一个反例。因为2,3之间的边没被覆盖。

  • 相关阅读:
    Django--form验证及错误处理
    Django--form保存用户输入内容
    Django--static静态文件引用
    Django--ajax
    Django--form基础
    Django--cookie&session
    Django--缓存
    Django--中间件
    oracle——session
    oracle——DDL
  • 原文地址:https://www.cnblogs.com/zzyDS/p/5537134.html
Copyright © 2011-2022 走看看