树型DP,树上的背包

遇到一个题目:
金矿问题
★问题描述
有一个国家,所有的国民都非常老实憨厚,某天他们在自己的国家发现了 M 座金
矿,国王知道这个消息后非常高兴,他希望能够把这些金子都挖出来造福国民。后他找
到了这个领域的专家小八,对每一座金矿进行勘测,勘测发现挖掘每个金矿所需的人数
是固定的(一个也不需要多,一个也不能少),而且这些金矿也有主矿和副矿的区别,
主矿可以直接进行挖掘,但是副矿必须在挖掘完其对应的主矿后才能进行挖掘,否则会
有相当的风险。每个主矿可能有 0 个、1 个或 2 个副矿,每个副矿都只对应 1 个主矿,
且副矿不会再有副矿。了解完这些后,国王决定派 N 个矿工去挖金矿。(N 个矿工不一定要全部用完)
★编程任务
给出金矿数量 M 及矿工数量 N ,以及每个金矿的具体情况。求最多能获得的金子数量。
★数据输入
第 1 行为两个正整数,用一个空格隔开:
NM
(其中 N(2<=N<=100),表示所有矿工数量,M(2<=M<=1000),为金矿的数量。)
从第 2 行到第 M+1 行,第 j 行给出了编号为 j-1 的金矿的基本数据,每行有 3 个
非负整数 v p q(其中 v 表示挖掘该金矿所需的矿工数目,p 表示该金矿的含金量,q 表示该金矿是主矿还是副矿。如果 q=0,表示该金矿是主矿,如果 q>0,表示该金矿为副矿,q
是所属主矿的编号)
★数据输出输出只有一行,表示最多的金子数量。
输入文件示例 输出文件示例
100 5
80 100 0
40 500 1
20 300 1
20 100 0
50 200 0 400

虽然最开始能想到是dp,但是以为状态之间存在依赖关系,所以真的无从下手被告知是树型dp,所以去看了下树上背包入门问题 hdu 1561

Problem Description
ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗?

Input
每个测试实例首先包括2个整数,N,M.(1 <= M <= N <= 200);在接下来的N行里,每行包括2个整数,a,b. 在第 i 行,a 代表要攻克第 i 个城堡必须先攻克第 a 个城堡,如果 a = 0 则代表可以直接攻克第 i 个城堡。b 代表第 i 个城堡的宝物数量, b >= 0。当N = 0, M = 0输入结束。

Output
对于每个测试实例,输出一个整数,代表ACboy攻克M个城堡所获得的最多宝物的数量。

Sample Input
3 2
0 1
0 2
0 3
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
0 0

Sample Output
5
13

这个题我们大致能抽象出这样的状态:
设状态 dp[i][k][j]:第i个节点,前k个子节点,该节点和它的子节点总共用了j次攻破所获得的最大收益
状态转移: dp[i][k][j]=max(dp[i][k-1][j],dp[i][k-1][p]+dp[son(i)][k-1][j-p]
这个状态的意义是:
dp[i][k-1][j]:第k个子节点不去攻破
dp[i][k-1][p]+dp[son(i)][k-1][j-p]:去攻破第k个子节点,但子节点也有孩子,所以分配的攻破次数要枚举

/* ***********************************************
Author        :Tisuama
Created Time  :2017年10月31日 星期二 16时49分32秒
File Name     :挖金矿.cpp
************************************************ */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLEAR( a , x ) memset ( a , x , sizeof a )  
const int INF=0x3f3f3f3f;
typedef long long LL; 
const int maxn=1e5+100;
int dp[220][220];
vector<int> v[220];
int val[220];
int vis[220];
int n,m;
void dfs(int f)
{
    vis[f]=1;
    for(int i=0;iint to=v[f][i];
        if(!vis[to]) dfs(to);
        for(int j=m;j>=1;j--)
        {
            for(int k=1;k<=j;k++)
            {
                dp[f][j]=max(dp[f][j],dp[f][k]+dp[to][j-k]);
            }
        }
    }
    return ;
}
int main()
{
    int a,b;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)
            break;
        m++;
        for(int i=0;i<=n;i++) v[i].clear();
        CLEAR(dp,0);
        CLEAR(vis,0);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);
            v[a].push_back(i);
            dp[i][1]=b;
        }
        dfs(0);
        printf("%d\n",dp[0][m]);
    }
}

那么回到这个题,这需要稍稍变一下,上面是分配攻破次数,我们这里分配人数

/* ***********************************************
Author        :Tisuama
Created Time  :2017年10月31日 星期二 21时16分19秒
File Name     :挖金矿.cpp
************************************************ */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLEAR( a , x ) memset ( a , x , sizeof a )  
const int INF=0x3f3f3f3f;
typedef long long LL; 
const int maxn=1e5+100;
int dp[1100][1100];
int n,m;
vector<int> v[1100];
int vis[1100],val[1100];
void dfs(int x)
{
    vis[x]=1;
    for(int i=0;iint to=v[x][i];
        if(!vis[to]) dfs(to);
        for(int j=n;j>=0;j--)
        {
            for(int k=val[x];k<=j;k++)
                dp[x][j]=max(dp[x][j],dp[x][k]+dp[to][j-k]);
        }
    }
    return ;
}

int main()
{    
    int x,y,z;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0;i<=n;i++) v[i].clear();
        CLEAR(dp,0);
        CLEAR(vis,0);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            v[z].push_back(i);
            dp[i][x]=y;
            val[i]=x;
        }
        dfs(0);
        printf("%d\n",dp[0][n]);        
    }
}

你可能感兴趣的:(动态规划)