树形dp入门

POJ 2342  Anniversary party

大概就是给你一棵树,每个点上有个权值,改点与改点的父节点不能都选,求选出来的点的权值之和最大值


方法:

dp[i][0]表示以i为根节点的树中不选i点的的最大权值和,dp[i][1]表示选i点的最大权值和

因为点i与儿子节点不能同时选,

所以

do[i][1]= sum( dp[j][0] ) 

dp[i][0]= sum( max (dp[j][1], dp[j][0])) //j为i的儿子节点

代码:

 

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 6006

int a[maxn], head[maxn], next[maxn];
int dp[maxn][2], n, h[maxn];

void dfs(int v)
{
  //  printf("%d\n", v);
    if(head[v]== -1)
    {
        dp[v][0]= 0;
        dp[v][1]= a[v];
    }
    else{
    int t1= 0, t0= 0;
    for(int i= head[v]; i!= -1; i= next[i])
    {
        dfs(i);
        t1+= dp[i][0];
        t0+= max(dp[i][1], dp[i][0]);
    }
    dp[v][1]= t1+ a[v];
    dp[v][0]= t0;
    }
 //   printf("%d %d %d\n", v, dp[v][1], dp[v][0]);
}

int main()
{
    while(scanf("%d %d",&n, &a[1])!=EOF )
    {
         for(int i= 2; i<= n; i++)
            scanf("%d",&a[i]);
         int son, fa;
         memset(head, -1, sizeof head);
         memset(h, 0, sizeof h);
         for(int i= 1; i<= n; i++)
         {
             scanf("%d %d",&son, &fa);
             h[son]= 1;
             next[son]= head[fa]; 
             head[fa]= son;
        }
        int v; //根节点
        for(int i= 1; i<= n; i++)
            if(!h[i])
            {
                v= i;
                break;
            }
        dfs(v);
        printf("%d\n", max(dp[v][0], dp[v][1]));
    }
    return 0;
}



 


hdu 1561 The more, The Better

 题意:

给你一棵N个节点的树,书上的每个点都有一个权值W【i】,从树上选出M个点(选点规则:选某点之前,必须先选改点的父亲节点),求M个点权值和最大值

1<= M<=N<= 200

方法:

dp[i][j]表示以i为根节点的子树中选取j个点的最大权值,那么dp[i][j]= max(dp[k1][j1] + dp[k2][j2] +..) 其中ki为i的子节点,j1+ j2+...= j-1

刚开始做的时候,我想到这里就不知道怎么做了,后面看了别人解题报告才想到,把求dp【i】【j】看成背包问题

ki表示第i个物品,ji表示这个物品选ji个, 所以我们可以把求dp[i][j] 看成n个物品,每个物品最多选j-1次的背包问题即可

代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 222

int w[maxn], head[maxn], next[maxn], dp[maxn][maxn], d[maxn];

void dfs(int n, int m)
{
    if(head[n]== -1 || m== 1)
    {
        for(int i= 1; i<= m; i++)
            dp[n][i]= w[n];
    }
    else
    {
        for(int i= head[n]; i!= -1; i= next[i])
            dfs(i, m-1);
        int d[maxn];
        memset(d, 0, sizeof d);
        for(int i= head[n]; i!= -1; i= next[i])
        for(int j= m-1; j>= 1; j--)
        for(int k= 0; k<= j; k++)
            d[j]= max(d[j], d[j-k] +dp[i][k]);  //这里转化成背包问题
         for(int i= 0; i<= m-1; i++)
                dp[n][i+1]= d[i]+ w[n];
    }
}

int main()
{
    int n, m;
    while(scanf("%d %d",&n,&m) && (n || m))
    {
        memset(head, -1, sizeof head);
        memset(w, 0, sizeof w);
        memset(dp, 0, sizeof dp);
        int a;
        for(int i= 1; i<= n; i++)
        {
            scanf("%d %d",&a, &w[i]);
            next[i]= head[a];
            head[a]= i;
        }
        dfs(0, m+1);
        printf("%d\n",dp[0][m+1]);
    }
    return 0;
}


wikioi 1163 访问艺术馆

题目链接:

http://codevs.cn/problem/1163/

感觉有点类似上题,不过比上题要简单

代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <stack>
using namespace std;
#define maxn 111

int t[maxn], v[maxn], head[maxn], next[maxn], dp[maxn][666];
stack<int>s;

void dfs(int n, int m)
{
    if(v[n])
    {
        for(int i= 2*t[n]; i<= m; i++)
            dp[n][i]= min((i- 2*t[n])/5, v[n]);
    }
    else
    {
        int aa= head[n];
        int bb= next[aa];
        if(m>= 2*t[n])
        {
            dfs(aa, m- 2*t[n]);
            dfs(bb, m- 2*t[n]);
        }
        for(int i= 0; i<= m- 2*t[n]; i++)
            for(int j= 0; j<= i; j++)
                dp[n][i+ 2*t[n]]= max(dp[n][i+ 2*t[n]], dp[aa][j]+ dp[bb][i-j]);
    }
}

 int main()
 {
    int time;
    scanf("%d",&time);
    scanf("%d %d",&t[1], &v[1]);
    if(!v[1])
    {
        s.push(1);
        s.push(1);
    }
    int tot= 1;
    memset(head, -1, sizeof head);
    while(!s.empty())
    {
        tot++;
        scanf("%d %d",&t[tot], &v[tot]);
        int f= s.top();
        s.pop();
        next[tot]= head[f];
        head[f]= tot;
        if(!v[tot])
        {
            s.push(tot);
            s.push(tot);
        }
    }
    dfs(1, time);
    printf("%d\n", dp[1][time]);
    return 0;
 }



 

hdu 1011  Starship Troopers
感觉和上上题差不多啊, 不过这题有个大坑啊 就是m=0 的时候, 原来是要输出0的,就算后面有花费为0的点也不能走

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

int dp[111][111], head[111], next[111], a[111], b[111];

void dfs(int n, int m)
{
    if(!m)
        return;// 此处时为了保证m=0的时候结果为0
    int MIN= a[n]/ 20;
    if(a[n]%20)
        MIN++;
    if(head[n]== -1) //叶子节点
    {
         for(int i= MIN; i<= m; i++)
             dp[n][i]= b[n];
    }
    else
    {
        int mm= m- MIN;
        if(mm>= 0)
        {
            for(int i= head[n]; i!= -1; i= next[i])
                dfs(i, mm);
            int d[111];
            memset(d, 0, sizeof d);
            for(int i= head[n]; i!= -1; i= next[i])
            for(int j= mm; j>= 1; j--)
            for(int k= 1; k<= j; k++)
                d[j]= max(d[j], d[j-k]+ dp[i][k]);
             for(int i= 0; i<= mm; i++)
                    dp[n][i+MIN]= d[i] + b[n];
        }
    }
}

int main()
{
    int n, m;
    while(scanf("%d %d",&n,&m) && (n!= -1 || m!= -1))
    {
        for(int i= 1; i<= n; i++)
            scanf("%d %d",&a[i], &b[i]);
        memset(head, -1, sizeof head);
        int f, son;
        for(int i= 1; i<= n-1; i++)
        {
            scanf("%d %d",&f, &son);
            if(f> son)
            {
                int temp= f;
                f= son;
                son= temp;
            }
            next[son]= head[f];
            head[f]= son;
        }
        memset(dp, 0, sizeof dp);
        dfs(1, m);
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
/*
1 0
0 7
2 0
0 10
0 10
2 1
*/






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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值