POJ 2342 Anniversary party(树形DP)

题目:   每个人有个rating值, 总rating值为在场的所有人的rating值之和,但有个要求:  每个人都不能和他的直接上司待在一起,问最大可能的rating值是多少?

分析: 所有人的关系构成了一棵树,我们可以用d[i]表示已i为根节点的子树的rating值之和。

节点i有两种决策:  选和不选。  如果选节点i,则问题转化为 i 的所有孙子rating值之和;若不选 i 则问题转换为 i 的所有儿子的rating值之和。转移方程为:

 d[ i ] = max { rating [ i ] + ∑ d[ j ]  ,  ∑ d[ k ] }              (rating[ i ] 表示 i 的rating值, j 为 i 的孙子 , k 为 i 的儿子)

这类问题就是所谓的树形DP。

代码:

#include <iostream>
#include <cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=6050;
int d[maxn],in[maxn],sum[maxn];   //  这里d[i]是rating [i]   ,  sum[i]是上面说的d[i] , 变量有些混乱了~~~
vector<int>son[maxn];  //son[i]存放i的所有儿子
int N;

int dfs(int u){   
    if(sum[u]!=-1) return sum[u];   //记忆化搜索
    int sn=0,gsn=0;             //sn指u的所有儿子rating之和 , gsn指u的所有孙子rating之和
    for(int i=0;i<son[u].size();i++){
        int v=son[u][i];
        sn += dfs(v);
        for(int j=0;j<son[v].size();j++){
            gsn += dfs(son[v][j]);
        }
    }
    return sum[u]=max(sn,gsn+d[u]);
}
int main()
{
    while(~scanf("%d",&N),N){
        for(int i=0;i<=N;i++) son[i].clear(),in[i]=0,sum[i]=-1;
        int x,y;
        for(int i=1;i<=N;i++) scanf("%d",&d[i]);
        for(int i=1;i<N;i++){
            scanf("%d%d",&x,&y);
            son[y].push_back(x);  in[x]++;
        }
        int u=1;
        for(int i=1;i<=N;i++){
            if(in[i]==0) { u=i; break; }
        }
        int ans=dfs(u);
        printf("%d\n",ans);
    }
    return 0;
}

用vector记录所有的儿子并不是很好的做法,其实可以从另一个角度看,也可以从i 节点出发去更新它的 父亲 和祖父的 d [ i ]值 ,这样每个节点需几录它的父亲是谁就可以了,这样做不论在时间还是空间上都是 一个很有效的该进。

同样给出代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int maxn=6010;
int fa[maxn],rate[maxn],son[maxn],gson[maxn],sonnum[maxn];  //分别指父亲节点,该节点权值,所有儿子节点权值之和,所有孙子节点权值加上它自身权值之和,儿子的个数
queue<int>que;
int N;
void Init(){
    memset(fa,0,(N+1)*sizeof(int));
    memset(sonnum,0,(N+1)*sizeof(int));
    memset(son,0,(N+1)*sizeof(int));
    memset(gson,0,(N+1)*sizeof(int));
    while(!que.empty()) que.pop();
}
int main()
{
    while(scanf("%d",&N),N){
        Init();
        for(int i=1;i<=N;i++) scanf("%d",&rate[i]);
        int L,K;
        for(int i=1;i<N;i++){
            scanf("%d%d",&L,&K);
            fa[L]=K,sonnum[K]++;
        }
        for(int i=1;i<=N;i++) {
            if(!sonnum[i]) que.push(i);
        }
        int u,f;
        while(!que.empty()){
            u=que.front(); que.pop();
            gson[u] += rate[u];
            f=fa[u];
            son[f] += max(son[u],gson[u]);
            gson[f] += son[u];
            if(f && --sonnum[f]==0) que.push(f);  //注意向上更新的顺序,只有当该节点的所有儿子节点更新完了(即 --sonnum[f]==0),才用它去更新它父亲,保证得到正确解
        }
        printf("%d\n",max(son[u],gson[u]));
    }
    return 0;
}


两种基本的方法都介绍了, 树形DP终于入了半边门了,O(∩_∩)O~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值