题意
nn 门功课形成一棵树,每门课有一个学分,选 门,选择一门课的前提是选择它的父亲,求最大学分。
1≤n,m≤3001≤n,m≤300
思路
这是一个树上的依赖背包问题。首先考虑暴力,设 dpi,jdpi,j 为 ii 这棵子树,选 门课时的最大学分,那将每棵子树当一个泛化物品,那么对于一个子树 kk ,有如下方程:
复杂度不难发现是 O(nm2)O(nm2) 的。
其实树上依赖背包问题有一个优化,我们从一般的背包开始分析:
对于 nn 个物品,总钱数为 。第 ii 个物品费用为 ,价值为 vivi 。设 dpi,jdpi,j 表示第 ii 个物品到第 个物品,花费为 jj 的最大收入,复杂度 。在树上,是否也可以套用这个思路呢?
我们也用 dpi,jdpi,j 表示 [i,n][i,n] 号节点,费用为 jj 时的最大价值。对于一个节点 ,选 jj 门课,讨论的问题莫过于是 节点选与不选,如果选,就转化成了它子树中的问题,如果不选,就转化成了从遍历完子树后第一个到的点开始的问题。直觉告诉我们,应该处理一个 dfsdfs 序(前序遍历的顺序),并处理每棵子树的大小(以“跳”出子树)。我们用 oriiorii 表示 dfsdfs 序为 ii 的节点的原编号,有如下方程:
其中 dpdp 的节点编号是 dfsdfs 序的编号。
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 303
typedef long long LL;
using namespace std;
template<const int maxn,const int maxm>struct Linked_list
{
int head[maxn],to[maxm],nxt[maxm],tot;
void clear(){memset(head,-1,sizeof(head));tot=0;}
void add(int u,int v){to[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
#define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
Linked_list<N,N>G;
int dp[N][N],dfn[N],ori[N],sz[N],ord;
int n,m,p[N];
void dfs(int u)
{
dfn[u]=++ord,ori[ord]=u,sz[u]=1;
EOR(i,G,u)
{
int v=G.to[i];
dfs(v);
sz[u]+=sz[v];
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
G.clear();
memset(dp,0,sizeof(dp));
FOR(v,1,n)
{
int u;
scanf("%d%d",&u,&p[v]);
G.add(u,v);
}
m++;n++;ord=0;
dfs(0);
DOR(i,n,1)
FOR(j,1,m)
dp[i][j]=max(dp[i+1][j-1]+p[ori[i]],dp[i+sz[ori[i]]][j]);
printf("%d\n",dp[1][m]);
}
return 0;
}