Update on 2022.7.13 \text{Update on 2022.7.13} Update on 2022.7.13:
一些闲话:依稀记得之前做过一道类似的题,但是怎么都找不到了 🥲。
Update \text{Update} Update:找到那篇博客了(就是这道题),竟然还是 2 2 2 年前写的。那时候还在 c s d n \rm csdn csdn 上呢。
首先题目限制实际上可以转化为选一个连通块,于是有一个 O ( n m 2 ) \mathcal{O}(nm^2) O(nm2) 的 d p \tt dp dp:令 d p ( i , j ) dp(i,j) dp(i,j) 为以 i i i 为子树(必选 i i i),花费为 j j j 的最大价值。每个连通块都在它深度最浅的点上计数了。可以发现,背包的瓶颈在于两个背包的合并是 O ( m 2 ) \mathcal O(m^2) O(m2) 的。
插入一个元素可以用单调队列做到 O ( m ) \mathcal O(m) O(m),所以我们尝试用 dfs 序考虑这个问题。但是用 dfs 序也就意味着只能考虑 dfs 序为 [ 1 , i ] [1,i] [1,i] 或 [ i , n ] [i,n] [i,n] 的一段区间,不妨就考虑 [ 1 , i ] [1,i] [1,i] 区间,我们无法求出某一个子树的贡献,只能算出根节点(也就是 dfs 序为 1 1 1)的贡献,且时间复杂度为 O ( n m ) \mathcal O(nm) O(nm).
于是可以想到点分治。复杂度降到 O ( n m log n ) \mathcal O(nm\log n) O(nmlogn).
为什么用点分治?因为它快啊。
我们发现,按题目要求买出的玩意一定是联通的。虽然点分治会是树的形态发生改变,但等到改变时其实已经不需要root了。(将包含root的处理完了)而且其可以保证遍历到所有点。
最后,我们可以用多重背包的二进制优化来做dp。
至于为什么d数组输入时要减去1,是因为我们保证要买一个,就直接算了。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 502, M = 4002;
int tot, dp[N][M], ans, siz[N], maxx[N], root, T, n, m, w[N], c[N], d[N], cnt, head[N], nxt[N << 1], dot[N << 1];
bool vis[N];
void addEdge(const int u, const int v) {
dot[++ cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
void init() {
cnt = 0;
ans = 0;
maxx[0] = n;
tot = n;
root = 0;
memset(vis, 0, sizeof vis);
memset(head, 0, sizeof head);
}
void getRoot(const int u, const int ba) {
maxx[u] = 0, siz[u] = 1;
for(int i = head[u]; i; i = nxt[i]) {
int v = dot[i];
if(v == ba || vis[v]) continue;
getRoot(v, u);
siz[u] += siz[v];
maxx[u] = max(maxx[u], siz[v]);
}
maxx[u] = max(maxx[u], tot - siz[u]);
if(maxx[root] > maxx[u]) root = u;
}
void cal(const int u, const int ba, const int lim) {
if(lim <= 0) return;
int i, j;
for(i = 1, j = d[u]; i < j; j -= i, i <<= 1)
for(int k = lim; k >= i * c[u]; -- k)
dp[u][k] = max(dp[u][k], dp[u][k - i * c[u]] + i * w[u]);
for(int k = lim; k >= j * c[u]; -- k)
dp[u][k] = max(dp[u][k], dp[u][k - j * c[u]] + j * w[u]);
for(i = head[u]; i; i = nxt[i]) {
int v = dot[i];
if(v == ba || vis[v]) continue;
for(j = 0; j <= lim - c[v]; ++ j) dp[v][j] = dp[u][j] + w[v];
cal(v, u, lim - c[v]);
for(j = 0; j <= lim - c[v]; ++ j) dp[u][j + c[v]] = max(dp[u][j + c[v]], dp[v][j]);
}
}
void divide(const int u) {
vis[u] = 1;
for(int i = 0; i <= m - c[u]; ++ i) dp[u][i] = w[u];
cal(u, 0, m - c[u]);
for(int i = 0; i <= m - c[u]; ++ i) ans = max(ans, dp[u][i]);
for(int i = head[u]; i; i = nxt[i]) {
int v = dot[i];
if(vis[v]) continue;
root = 0;
tot = siz[v];
getRoot(v, 0);
divide(root);
}
}
int main() {
int a, b;
scanf("%d", &T);
while(T --) {
scanf("%d %d", &n, &m);
init();
for(int i = 1; i <= n; ++ i) scanf("%d", &w[i]);
for(int i = 1; i <= n; ++ i) scanf("%d", &c[i]);
for(int i = 1; i <= n; ++ i) scanf("%d", &d[i]), -- d[i];
for(int i = 1; i < n; ++ i) {
scanf("%d %d", &a, &b);
addEdge(a, b); addEdge(b, a);
}
getRoot(1, 0);
divide(root);
printf("%d\n", ans);
}
return 0;
}