zoukankan      html  css  js  c++  java
  • [vijos1144]小胖守皇宫<树形dp>

    题目链接:https://vijos.org/p/1144

    woc我竟然A了,这道经典的树形dp或者说是树形dp的入门题我终于过了,虽然之前做过一些树形dp的题,但是这题开始还是一脸懵逼,dp方程如何定义都知道,但是不懂转移啊,这就有点伤了。。

    dp方程定义dp[i][1]节点i 选自己

         dp[i][2]节点i选自己的儿子==不选自己和父亲

            dp[i][3]节点i选自己的父亲==不选自己选父亲

    然后就是转移了。。毕竟是基础题嘛,所以转移也不难

    转移的时候我们是直接递归到叶节点然后再做前面的。。所以我们不用考虑父节点的状态

    dp[i][1]选自己的时候,儿子节点就有两种方式,选儿子自己,或者选儿子的父亲dp[i][1]+=min(dp[son][1],dp[son][3]);

    dp[i][2]选儿子时 ,儿子就选或不选两个方式,但是如果一旦所有的儿子都是不选了,我们就要找一个最小的儿子树的值在最后加上,

    dp[i][2]+=min(dp[son][1],dp[son][2])如果全部选了2,就要在结尾加上dp[i][2]+=min(dp[son][1]-dp[son][2])

    至于为啥加这个,就是这道题唯一有点思考难度的地方了,因为你是要加最小的儿子选一个的值,所以找到最小,比如时第s2个儿子,之前已经加了dp[s2][2],所以最后的时候是加上dp[s2][1]-dp[s2][2],相当于把之前的那个dp[s2][2]抵消了,就不会加重复

    dp[i][3]选父亲时,因为我们是从子往父推,所以不从父亲转移,就考虑这时候的儿子节点,儿子节点来源就是儿子自己放和儿子的儿子放

    dp[i][3]+=min(dp[son][1],dp[son][2]);

    好吧这就是这道题的全部了,最后只需要输出根节点的选自己和选儿子方案的最小值就可,因为根没有父亲。。。

    储存这个关系的方式有两种,一种是链表,一种是多叉树转二叉树,

    两种方式的不同点在于链表是双向的,然后重新建树,默认1为根节点

    而多叉树转二叉树是以题目给的关系建树,以没有父亲的点为根

    链表:

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #include<algorithm>
     5 #include<cstdlib>
     6 #include<cmath>
     7 #include<queue>
     8 #define maxn 1505
     9 using namespace std;
    10 
    11 int f[maxn][4],n;
    12 struct edge{
    13     int u,v,w,nxt;
    14 }e[maxn*10];
    15 int a[maxn],head[maxn],vis[maxn],tot;
    16 
    17 void adde(int u,int v){
    18     tot++;
    19     e[tot].u=u;e[tot].v=v;
    20     e[tot].nxt=head[u];
    21     head[u]=tot;
    22 }
    23 
    24 void work(int x){
    25     f[x][1]=a[x];
    26     int s=0x3f3f3f,p=0;
    27     for(int i=head[x];i!=-1;i=e[i].nxt){
    28         int v=e[i].v;
    29         if(vis[v]==1)continue;
    30         vis[v]=1;
    31         work(v);
    32         f[x][1]+=min(f[v][1],f[v][3]);
    33         if(f[v][1]<f[v][2]){
    34             f[x][2]+=f[v][1],p=1;
    35         }else{
    36             f[x][2]+=f[v][2],s=min(s,f[v][1]-f[v][2]);
    37         }
    38         f[x][3]+=min(f[v][1],f[v][2]);
    39     }
    40     if(p==0)f[x][2]+=s;
    41 }
    42 
    43 int main(){
    44     memset(head,-1,sizeof(head));
    45     scanf("%d",&n);
    46     for(int i=1;i<=n;i++){
    47         int num,val,sum;
    48         scanf("%d%d%d",&num,&val,&sum);
    49         a[num]=val;
    50         for(int j=1;j<=sum;j++){
    51             int b;
    52             scanf("%d",&b);
    53             adde(num,b);adde(b,num);
    54         }
    55     }
    56     vis[1]=1;work(1);
    57     printf("%d",min(f[1][1],f[1][2]));
    58 }
    View Code

    多叉树转二叉树

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #include<algorithm>
     5 #include<cstdlib>
     6 #include<cmath>
     7 #include<queue>
     8 #define maxn 3005
     9 using namespace std;
    10 
    11 int n,m,a[maxn],root;
    12 int f[maxn][5],vis[maxn];
    13 int lson[maxn],rson[maxn],fa[maxn];
    14 
    15 void work(int x)
    16 {
    17     f[x][1]=a[x];
    18     int s=0x3f3f3f,p=0;
    19     for(int i=lson[x];i!=0;i=rson[i]){
    20         work(i);
    21         if(f[i][2]<f[i][1]){
    22             f[x][2]+=f[i][2];s=min(f[i][1]-f[i][2],s);
    23         }else f[x][2]+=f[i][1],p=1;
    24         f[x][1]+=min(f[i][3],f[i][1]);
    25         f[x][3]+=min(f[i][1],f[i][2]);
    26     }
    27     if(p==0)f[x][2]+=s;
    28 }
    29 
    30 int main()
    31 {
    32     scanf("%d",&n);
    33     for(int i=1;i<=n;i++){
    34         int num,val,q;
    35         scanf("%d%d%d",&num,&val,&q);
    36         a[num]=val;
    37         for(int j=1;j<=q;j++){
    38             int s,now=lson[num];
    39             scanf("%d",&s);
    40             fa[s]=num;
    41             if(j==1)lson[num]=s;
    42             else {
    43                 while(rson[now]!=0){
    44                     now=rson[now];
    45                 }
    46                 rson[now]=s;
    47             }
    48         }
    49     }
    50     for(int i=1;i<=n;i++)
    51      if(fa[i]==0)work(i),root=i;
    52     printf("%d",min(f[root][1],f[root][2]));
    53 }
    View Code

    提醒一点,多叉树转二叉树需要在执行dp之前跑个O(n)找到根节点

  • 相关阅读:
    分治法求最大子序列
    6.2 链表 (UVa 11988, 12657)
    6.1 栈和队列 (UVa 210, 514, 442)
    S-Tree (UVa 712) 二叉树
    Equilibrium Mobile (UVa 12166) dfs二叉树
    Patrol Robot (UVa 1600) BFS
    Knight Moves (UVa 439) BFS
    Tree Recovery (UVa 536) 递归遍历二叉树
    Parentheses Balance (Uva 673) 栈
    Self-Assembly (UVa 1572)
  • 原文地址:https://www.cnblogs.com/Danzel-Aria233/p/7665845.html
Copyright © 2011-2022 走看看