树上背包1
http://oj.daimayuan.top/course/8/problem/269
设
d
p
[
u
]
[
m
]
dp[u][m]
dp[u][m] 为询问的答案,每次新算出一个儿子就枚举当前的背包容量和儿子的背包容量去合并成新的背包。题目求的是包含
u
u
u 的连通块,所以初始时
d
p
[
u
]
[
0
]
dp[u][0]
dp[u][0] 应为负无穷,因为不能不选
u
u
u ,算完
u
u
u 的答案后应将
d
p
[
u
]
[
0
]
dp[u][0]
dp[u][0] 置
0
0
0 ,因为算其父节点的时候可以不选
u
u
u 子树。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3 + 10;
int n, q, a[N], f[N][N], sz[N], tmp[N];
vector<int> g[N];
void dfs(int u) {
sz[u] = 1;
f[u][1] = a[u];
for (auto v : g[u]) {
dfs(v);
for (int i = 0; i <= sz[u] + sz[v]; ++i) tmp[i] = -235802127;
/*
这个二重循环在枚举两个背包的各自的所有可能的容量去合并,实际上
等价于两个子树内的两两点对算一个贡献,而已经被算过的点对不会再次被计算,
那么从宏观看,最终也就是枚举了整个树上不重复的点对,也就只有 n 方个,所以复杂度正确
*/
for (int i = 0; i <= sz[u]; ++i) {
for (int j = 0; j <= sz[v]; ++j) {
tmp[i + j] = max(tmp[i + j], f[u][i] + f[v][j]);
}
}
for (int i = 0; i <= sz[u] + sz[v]; ++i) {
f[u][i] = tmp[i];
}
sz[u] += sz[v];
}
f[u][0] = 0;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
memset(f, -0xf, sizeof f);
scanf("%d%d", &n, &q);
for (int i = 2; i <= n; ++i) {
int fa; scanf("%d", &fa);
g[fa].push_back(i);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
dfs(1);
while(q--) {
int u, m;
scanf("%d%d", &u, &m);
printf("%d\n", f[u][m]);
}
}
树上背包2
http://oj.daimayuan.top/course/8/problem/270
相比于
1
1
1 来说,
n
n
n 的范围增大,
m
m
m 的范围减小,我们可以在枚举背包容量的时候有
m
m
m 的上限,这样复杂度就保证了是
O
(
n
∗
m
)
O(n*m)
O(n∗m)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4 + 5;
const int M = 1e2 + 5;
int n, q, a[N], f[N][M], sz[N], tmp[M];
vector<int> g[N];
void dfs(int u) {
sz[u] = 1;
f[u][1] = a[u];
for (auto v : g[u]) {
dfs(v);
for (int i = 0; i <= min(sz[u] + sz[v], 100); ++i) tmp[i] = -235802127;
/*
下列二重循环的整体复杂度为 n*m
*/
for (int i = 0; i <= sz[u]; ++i) {
for (int j = 0; j <= sz[v] && i + j <= 100; ++j) {
tmp[i + j] = max(tmp[i + j], f[u][i] + f[v][j]);
}
}
for (int i = 0; i <= min(sz[u] + sz[v], 100); ++i) {
f[u][i] = tmp[i];
}
sz[u] += sz[v];
}
f[u][0] = 0;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
memset(f, -0xf, sizeof f);
scanf("%d%d", &n, &q);
for (int i = 2; i <= n; ++i) {
int fa; scanf("%d", &fa);
g[fa].push_back(i);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
dfs(1);
while(q--) {
int u, m;
scanf("%d%d", &u, &m);
printf("%d\n", f[u][m]);
}
}
树上背包3
http://oj.daimayuan.top/course/8/problem/271
这题每个点的权重不是
1
1
1 了,所以用树上背包
2
2
2 的方法合并两个背包会达到
O
(
n
×
m
2
)
O(n \times m^2)
O(n×m2) 的复杂度。这里设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为考虑
d
f
s
dfs
dfs 序在
[
i
,
n
]
[i,n]
[i,n] 的节点,选出容量为
j
j
j 的最大价值,且选出来的点保证不存在某个点被选了,其在
[
i
,
n
]
[i,n]
[i,n] 内的祖先没有被选。
这里设
d
f
s
dfs
dfs 序为
i
i
i 的子树大小为
s
z
[
i
]
sz[i]
sz[i] ,则若不选
i
i
i 点,则
i
+
s
z
[
i
]
−
1
i+sz[i]-1
i+sz[i]−1 的点都不能选,则此时的情况等价于
d
p
[
i
+
s
z
[
i
]
]
[
j
]
dp[i+sz[i]][j]
dp[i+sz[i]][j] 。倘若选择
i
i
i ,那么对于
d
p
[
i
+
1
]
dp[i+1]
dp[i+1] 的所有状态来说就是多了一个必选的点,而多选一个点对
d
p
dp
dp 数组含义中限制条件是没有任何影响的,所以就等价于
d
p
[
i
+
1
]
[
j
−
w
[
i
]
]
+
a
[
i
]
dp[i+1][j-w[i]]+a[i]
dp[i+1][j−w[i]]+a[i] ,最终也就是:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
+
s
z
[
i
]
]
[
j
]
,
d
p
[
i
+
1
]
[
j
−
w
[
i
]
]
+
a
[
i
]
)
dp[i][j] = max(dp[i+sz[i]][j],dp[i+1][j-w[i]]+a[i])
dp[i][j]=max(dp[i+sz[i]][j],dp[i+1][j−w[i]]+a[i])
后续遍历是因为
d
f
s
dfs
dfs 序为
n
n
n 肯定是个叶子,所以叶子的状态是可以直接求得的,所以可以不断往前推,最终得到正确答案。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 5;
const int M = 1e4 + 5;
const int INF = -235802127;
int n, m, a[N], w[N], f[N][M], sz[N], dfn[N], rk[N], tot;
vector<int> g[N];
void dfs(int u) {
++tot; dfn[u] = tot;
rk[dfn[u]] = u;
sz[dfn[u]] = 1;
for (auto v : g[u]) {
dfs(v);
sz[dfn[u]] += sz[dfn[v]];
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
memset(f, -0xf, sizeof f);
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; ++i) {
int fa; scanf("%d", &fa);
g[fa].push_back(i);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &w[i]);
}
dfs(1);
f[n + 1][0] = 0;
for (int i = n; i >= 1; --i) {
for (int j = 0; j <= m; ++j) {
f[i][j] = f[i + sz[i]][j];
if (j - w[rk[i]] >= 0 && f[i + 1][j - w[rk[i]]] != INF) {
f[i][j] = max(f[i][j], f[i + 1][j - w[rk[i]]] + a[rk[i]]);
}
}
}
for (int i = 0; i <= m; ++i) {
printf("%d\n", f[1][i] == INF ? 0 : f[1][i]);
}
}