#本题算法:树上背包
借着本题对这几天做的树上dp做一些总结
#题目及翻译:
传送门:Problem - 1011 (hdu.edu.cn)
翻译:有一棵n个节点的树,每个点有花费bugi,每个点的实际花费为(bugi/20)(向上取整),每个点的价值是braini,求在花费不超过m的情况下的最大价值。
#题解:
树上dp的思想其实非常简单,就是把在线性数组里进行的动态规划给放到新的数据结构——树上进行(约等于没说)
本题很明显就是一个典型的01背包问题,在有限的容量m下,实现最大的价值。
首先我们复习一下背包问题(线性的)
线性数据结构上的背包问题:
经典例题:洛谷p1048
传送门:P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
然后直接看状态转移方程部分:
二维:
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);
具体含义不再赘述(我猜大家都会,不会的可以看一看p1048的题解),将这个二维的转移方程转化为树形dp的转移方程会变成三维dp,空间会爆掉,所以这个转移方程无法进行下去。
一维:
f[j] = max(f[j], f[j - w[i]] + v[i]);
这个转移方程已经优化了空间复杂度,代表了在总容量为i的时候物品价值和的最大值。
所以我们拿这个状态转移方程去修改成树形dp的状态转移方程
dp[k][j+l] = max(dp[k][j+l],dp[k][j]+dp[son][l]);
dp[i][j] 表示 以第i号节点为父节点,使用j个容量值所能含有的最大价值
状态为i点放置l个士兵,保证l+j<=m就可以
之后直接dfs下来就可以了。
#细节处理:
感谢这篇题解给我关于向上取整ceil()的启示。
初始化记得存双向边
#代码:
#include<bits/stdc++.h>
#define MAXN 100005
#define N 105
using namespace std;
struct node
{
int bug;
int brain;
}p[MAXN];
bool visited[MAXN];
vector<int> tree[MAXN];
int dp[N][N];
int n,m;
void init()
{
memset(dp,0,sizeof(dp));
memset(visited,true,sizeof(visited));
for(int i = 0; i <= n; i++)
{
tree[i].clear();
}
for(int i = 1; i <= n; i++)
{
cin>>p[i].bug>>p[i].brain;
p[i].bug = ceil(p[i].bug/20.0);
if(p[i].bug > m)
{
visited[i] = false;
}
}
for(int i = 1; i < n; i++)
{
int a,b;
cin>>a>>b;
tree[a].push_back(b);
tree[b].push_back(a);//存双向边
}
}
void dfs(int k)
{
visited[k] = false;
for(int i = p[k].bug; i <= m; i++)
{
dp[k][i] = p[k].brain;
}
for(int i = 0; i < tree[k].size(); i++)
{
int son = tree[k][i];
if(!visited[son])
{
continue;
}
dfs(son);
for(int j = m; j >= p[k].bug; j--)
{
for(int l = 1; l + j <= m; l++)
{
if(dp[son][l])
{
dp[k][j+l] = max(dp[k][j+l],dp[k][j]+dp[son][l]);
}
}
}
}
}
int main()
{
while(cin>>n>>m)
{
if(n == -1 && m == -1)
{
break;
}
else
{
init();
if(!m)
{
cout<<"0"<<endl;
continue;
}
dfs(1);
cout<<dp[1][m]<<endl;
}
}
return 0;
}
完结撒花~