暂无链接
背包问题
【问题描述】
给你一棵树以1号节点为根的树,每个节点上有一个体积为v,价值为w的物品。现在要你统计,对于所有点i,如果只能取子树i中的物品,则容积为m的背包至多能装总价值多少的物品。
【输入格式】
第一行两个整数n,m,分别表示树的节点个数和背包容积。
接下来n行,每行两个整数,分别表示每个点上物品的价值和体积。
接下来一行n-1个整数,分别表示2-n号节点的父节点。保证 fai<i f a i < i 。
【输出格式】
一行n个整数,依次表示编号为i的子树的答案。
【输入样例】
13 6
4 7699
2 8393
4 3023
6 4775
1 998
3 7194
5 8194
1 7912
4 1874
6 5298
5 359
6 6171
1 9627
1 1 2 4 4 4 3 1 9 6 1 12
【输出样例】
26930 16585 10935 9192 998 7194 8194 7912 5298 5298 359 9627 9627
【数据范围】
对于30%的数据:n,m ≤ 500
对于60%的数据:fa[i]在[1,i)间等概率随机生成。
对于100%的数据:n ≤ 30000,m ≤ 500。
题解
本题区分了考试前一天学三十二叉堆和学 dsu on tree d s u o n t r e e 的同学,而我就是那个去学三十二叉堆的。
不过 O(n2m) O ( n 2 m ) 暴力背包居然有 60 60 分,感谢张瀚文大爷不杀之恩orz。。。
感觉 dsu on tree d s u o n t r e e 非常暴力,先递归处理轻儿子做背包,再递归做重儿子,然后增量式的插入根以及其他儿子的信息。
放在这道题上,就是先递归做轻儿子的背包,求出轻儿子的答案,然后递归下去做重儿子的,等重链做完了再遍历一遍轻儿子暴力插入重链得到的背包。
复杂度证明和树剖类似, dsu on tree d s u o n t r e e 相当于采用了重链剖分方式,这样暴力插入的次数不超过 O(nlog2n) O ( n l o g 2 n ) 次,总复杂度为 O(nmlog2n) O ( n m l o g 2 n ) 。
代码
顺便学习一波 C C ++的姿势
#include<bits/stdc++.h>
using namespace std;
const int M=3e4+5;
int cap[M],val[M],siz[M],son[M],ans[M],dp[505],n,m,a;
vector<int>mmp[M];
void dfs(int v){siz[v]=1;for(int to:mmp[v]){dfs(to);siz[v]+=siz[to];if(siz[to]>siz[son[v]])son[v]=to;}}
void ins(int v,int w){for(int i=m-v;i>=0;--i)dp[i+v]=max(dp[i+v],dp[i]+w);}
void ins(int v){ins(cap[v],val[v]);for(int to:mmp[v])ins(to);}
void dsu(int v,int p)
{
for(int to:mmp[v])if(to!=son[v])dsu(to,0);if(son[v])dsu(son[v],1);ins(cap[v],val[v]);
for(int to:mmp[v])if(to!=son[v])ins(to);ans[v]=dp[m];if(!p)memset(dp,0,sizeof(dp));
}
void in()
{
scanf("%d%d",&n,&m);for(int i=1;i<=n;++i)scanf("%d%d",&cap[i],&val[i]);
for(int i=2;i<=n;++i)scanf("%d",&a),mmp[a].push_back(i);
}
void ac(){dfs(1);dsu(1,0);for(int i=1;i<=n;++i)printf("%d ",ans[i]);}
int main(){in(),ac();}