[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)找到根节点

posted @ 2017-10-14 10:13  Danzel♂  阅读(124)  评论(0编辑  收藏  举报