题目链接: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 }
多叉树转二叉树
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 }
提醒一点,多叉树转二叉树需要在执行dp之前跑个O(n)找到根节点