树形背包入门————
写了一道树形背包题目,想了挺久的,把我的思路分享一下。
这里引用一道基础树形背包题目来配合讲解。
洛谷:P2014 CTSC1997选课
题意:在森林中每个点都有权值,选择M个点,使得总权值最大。(选择一个点,当且仅当它是根节点或者它的父节点被选择)
感觉对于我这种不太聪明的初学者很不友好,dp讲究一个寻找子问题,区间dp和线性dp一般直接根据题意找子问题在推出更大的问题。这道题虽然也要寻找子问题,但是不太容易,我们先把自己代入进去,看看能不能找到什么大问题和小问题的关联。
比如这颗树,你想象你是根节点,然后你要选3个点,3个点可以当成3元钱,选一个点花一块钱,那你需要考虑什么呢?
首先是选哪些点比较划算呢?对于根,有两种选择,老大和老二,那么第一状态就出来了,就是选择的结点。
假如我选老大,那么我还要考虑我要在老大这边花多少钱。
第二状态就出来了,分配的钱数。
状态找出来之后,目前就可以用dp[i][j]表示给i号结点j元钱之后,他能带给你的最大回报。
由于选点的连续性,你要选了之后才知道答案,这时候老大就说了,放心,把钱交给我,我告诉你答案,那么你把钱交给他了,他也面临同样的问题,于是把钱交给4号,4号交给7号,7号是叶子节点,同时也是最终子问题,7号就说了只要给我钱,给我多少都只能在我这里拿到相同的权值a[7]。4号得到了反馈,就说,给我一块钱可以拿到a[4],给我两块钱以上可以拿到a[4]+a[7]…如此层层反馈,问题便得到解决了。。
根据上述描述,可以发现是一种递归的性质,没错,树形背包需要靠dfs的递归来完成,还有一点,就是这个图可能是一个森林,我们用一个0节点把其他根节点连起来,变成一棵树,方便处理,最后输出dp[0][m+1]。
其实背包问题考虑的就是我有多少钱?买哪个东西?花多少钱?
下面给出代码与解释。
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
const int MAXN=2050;
using namespace std;
int n,m,cnt,a[MAXN],h[MAXN],dep[MAXN],s[MAXN],maxd[MAXN],val[MAXN],dp[500][500];
struct tu ///这里邻接表
{
int next,to,from,val;
}e[MAXN];
void add(int x,int y) ///邻接表连边
{
e[++cnt].to=y;
e[cnt].next=h[x];
h[x]=cnt;
}
void dfs(int now,int fath) ///核心
{
for(int i=h[now];i;i=e[i].next){
///老大还是老二?
int v=e[i].to;
if(v!=fath){
///这个点不能是父节点
dfs2(v,now);
for(int j=m+1;j>=1;--j) ///当前点初始有多少钱?
for(int k=0;k<j;++k) ///分配给下个点多少钱?
dp[now][j]=max(dp[now][j-k]+dp[v][k],dp[now][j]);
}
}
}
int main()
{
int i,j,k,l,r;
cin >> n >> m ;
for(i=1;i<=n;++i){
scanf("%d%d",&j,&k);
a[i]=k;
if(j){
add(j,i);s[i]=1;}
for(int j=m+1;j>=1;--j)
dp[i][j]=max(dp[i][j],a[i]);
}
for(i=1;i<=n;++i)
if(!s[i])add(0,i);
dfs2(0,0);
cout << dp[0][m+1] << endl ;
return 0;
}
<----To be continue