Solution [NOIP提高组2006]金明的预算方案
题目大意:给定一系列物品,每个物品有一个价格\(v\)和权值\(w\),并且物品间存在依赖关系.求一种可行方案,使得满足依赖关系并且\(\sum v \leq m\)的情况下\(\sum w\)尽量大
分析:这题很明显告诉你它的依赖关系是一颗树,比如如果一个物品\(v\)依赖物品\(u\),我们连一条边\(u,v\).为了方便,我们加入一个虚拟点\(0\),一个点\(u\)如果是主件我们就连\((0,u)\)
那么这样我们最后会得到一颗树,这个树有哪些特点呢?
- 一个物品所依赖的所有物品都是他的父节点
- 如果一个物品不选,对应到树上,他所有的子节点都不能选
好了,树构建出来了.我们来看如何解题.首先我们可以用树形dp来求解,但是这样做比较复杂,而且跑的不是很快.那么可不可以用我们比较熟悉的\(01\)背包来求解呢?
普通\(01\)背包的状态以及转移方程?
\(f[i][j] = max\begin{cases} f[i - 1][j] \\ f[i - 1][j - v] + w & j \geq v\end{cases}\)
这个相信大家十分熟悉,再看这题?树形\(dp\)不好做,我们可以把它变成线性dp.把一棵树变成一个序列我们可以怎么办?\(dfs\)序啊
我们对这颗树求一个后序遍历,这样一个节点的儿子以及左边兄弟在序列中都在它的前部.然后呢?每一步我们都有两种决策.
- 选这个物品,那么状态\(i\)可以直接由状态\(i - 1\)转移而来
- 不选这个物品,那么是不是这个点的儿子都不可以选,那么状态\(i\)就只能由它的左兄弟转移而来
整理一下,如果用\(pre[u]\)表示编号为\(u\)的左兄弟的编号的话:
\(f[i][j] = max\begin{cases}f[pre[i]][j]\\f[i - 1][j - v] + w & j \geq v \end{cases}\)
分析一下时间复杂度?求后序遍历以及\(pre\)我们都可以通过一次\(dfs\)在\(O(n)\)的时间内求出来
- 状态总数:\(nm\)
- 转移代价:\(O(1)\)
那么总复杂度?\(O(n + nm)\)所以总复杂度为\(O(nm)\)与\(01\)背包相当,而且这种算法还可以处理附件有附件等(也就是树的高度不只为\(2\))的情况
附上丑陋的代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 64;
const int maxm = maxn;
const int maxv = 33333;
struct Node{//表示物品
int v,w;
Node() = default;
Node(int a,int b):v(a),w(b){}
}d[maxn],t[maxn];
struct Edge{
int from,to;
Edge() = default;
Edge(int a,int b):from(a),to(b){}
}Edges[maxm];
int head[maxn],nxt[maxm],root[maxn],rp;
inline void addedge(int from,int to){
static int tot;
Edges[++tot] = Edge(from,to);
nxt[tot] = head[from];
head[from] = tot;
}
int pre[maxn],f[maxn][maxv],p = 0;
void dfs(int u){
int tp = p;//由于后序遍历的性质,一个点的左兄弟显然是进入这个点时序列中的最后一个点
for(int i = head[u];i;i = nxt[i]){
Edge &e = Edges[i];
dfs(e.to);
}
d[++p] = t[u];//后序遍历
pre[p] = tp;//求左兄弟,注意,pre[t]表示序列中编号为t的节点的左兄弟的编号
}
int n,m;
int main(){
#ifdef LOCAL
freopen("fafa.in","r",stdin);
#endif
scanf("%d %d",&m,&n);
for(int i = 1;i <= n;i++){//建图
int v,w,faz;
scanf("%d %d %d",&v,&w,&faz);
t[i] = Node(v,v * w);//先预处理它的权值
addedge(faz,i);//有个技巧,如果一个点是主件,我们就认为它依赖于虚拟点0
}
dfs(0);
for(int i = 1;i <= n;i++)//dp求解
for(int j = 0;j <= m;j++)
if(j >= d[i].v)f[i][j] = max(f[pre[i]][j],f[i - 1][j - d[i].v] + d[i].w);
else f[i][j] = f[pre[i]][j];//转移
printf("%d\n",f[n][m]);
return 0;
}