关于树上背包
在背包问题中有这么一类问题蛮特殊--树上背包
树上背包的问题就特殊在会有依赖性,然后就是一整个时间复杂度的小trick。
ex-1:
给定n()个点的有根树, 每个点有权值,权值可能为负数。
多组询问,询问以u为根的子树,个数恰好为m()的,包含u点的连通块的最大权值和。
很明显的树上背包问题
状态数量是量级的,转移是的,所以很多人可能会认为这样的复杂度无法通过本题。
其实不然,这样的题目就是树上背包的典型trick。
void dfs(int u)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
for (int k = 0; k <= sz[u] + sz[j]; k ++ ) tmp[k] = -INF;
for (int _i = 0; _i <= sz[u]; _i ++ )
for (int _j = 0; _j <= sz[j]; _j ++ )
tmp[_i + _j] = max(tmp[_i + _j], f[u][_i] + f[j][_j]);
for (int k = 0; k <= sz[u] + sz[j]; k ++ ) f[u][k] = tmp[k];
sz[u] += sz[j];
}
sz[u] ++ ;
for (int i = sz[u]; i > 0; i -- )
f[u][i] = f[u][i - 1] + a[u];
f[u][0] = 0;
}
只列出了重要的函数,可以发现是三个for循环嵌套的。
通过势能的一些分析最终可以得出,这个函数的时间复杂度就是的
所以其实这样的题目是可以通过这样比较暴力的思路通过的。
ex-2:
其他的都和上面一样,()()
void dfs(int u)
{
f[u][0] = 0;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
for (int k = 0; k <= min(V, sz[u] + sz[j]); k ++ ) tmp[k] = -INF;
for (int _i = 0; _i <= min(sz[u], V); _i ++ )
for (int _j = 0; _j <= min(V - _i, sz[j]); _j ++ )
if (_i + _j <= V) tmp[_i + _j] = max(tmp[_i + _j], f[u][_i] + f[j][_j]);
for (int k = 0; k <= min(sz[u] + sz[j], V); k ++ ) f[u][k] = tmp[k];
sz[u] += sz[j];
}
sz[u] ++ ;
for (int i = min(sz[u], V); i > 0; i -- )
f[u][i] = f[u][i - 1] + a[u];
f[u][0] = 0;
}
这样的代码又很像o()
但其实又不然,这样和每个size取完min后,可以证明出这个函数的时间复杂度为
至此这个时间复杂度就足以通过本题
ex-3:
其他的都和上面一样,但是物品有重量(n\leq1000)(m\leq10000)
这时好像那种分析时间复杂度的方法就不太适用了。
因为毕竟很多地方没办法剪掉,而是要把所有体积都枚举到,时间复杂度是妥妥的
在这种情况下,这个题目的解法就有些诡异了,而且非常的没有拓展性,也纯属是因为在分享树上背包把这个数据范围拿出来。
正解是使用树的欧拉序来DP(给出代码:)
void solve()
{
init();
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 2; i <= n; i ++ )
{
int fa;
cin >> fa;
add(fa, i, 1);
}
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];
dfs(1);
memset(f[n + 1] + 1, -0x3f, 4 * (m));
for (int i = tot; i >= 1; i -- )
{
int u = id[i];
for (int j = 0; j <= m; j ++ )
{
f[i][j] = f[r[u] + 1][j];
if (j >= b[u])
f[i][j] = max(f[i][j], f[i + 1][j - b[u]] + a[u]);
}
}
for (int i = 0; i <= m; i ++ )
if (f[1][i] >= 0) cout << f[1][i] << endl;
else cout << 0 << endl;
}
这样的时间复杂度就来到了
了解一下就可吧,感觉这个思想没办法扩展了。
最后总结一下就是,遇到这样的树上背包,尤其要注意时间复杂度的trick。
如果你发现不了这个trick,应该还是很难通过的。
✅✅✅
树上背包就说到这吧~