题目大意:滑稽树上滑稽果,滑稽树下你和我,滑稽树前做游戏,滑稽多又多。树上有 $n$ 个节点,它们构成了一棵树,每个节点都有一个滑稽值 $a_i$,每次你可以选择一个最大滑稽值和最小滑稽值不超过 $d$ 的连通块并把它们删掉,请问你最少能用几次把这些节点都删掉?$n,d,a_i \leq 5000$
我们可以设 $f[i][j][k]$ 表示使得以 $i$ 为根的连通块的最大值为 $j$ 最小值为 $k$ 的最少步数,这样的DP是 $O(n^3)$ 的。
考虑在数轴上以每个 $a_i$ 为左端点画一个长度为 $d$ 的区间,如图
那么只有区间有交集的节点才可以同时选择。
考虑每个区间的意义,它表示每个节点所在连通块的最大值的取值范围,相当于化简了DP数组的最小值那一维。
设 $f[i][j]$ 表示使得以 $i$ 为根的连通块的最大值为 $j$ 的最小步数,$g[i]$ 表示删除子树 $i$ 的最小步数。
DP转移方程见代码。
#include <cstdio>
#include <cstring>
const int N = 5005;
int n, d, a[N], f[N][N], g[N], head[N], nxt[N<<1], v[N<<1], tot;
int min(int x, int y) {
return x < y ? x : y;
}
void add_edge(int from, int to) {
nxt[++tot] = head[from];
head[from] = tot, v[tot] = to;
}
void dfs(int u, int fa) {
for (int i = a[u]; i <= a[u] + d && i <= 5000; ++i)
f[u][i] = 0;
for (int i = head[u]; i; i = nxt[i]) {
if (v[i] == fa) continue;
dfs(v[i], u);
for (int j = a[u]; j <= a[u] + d && j <= 5000; ++j)
f[u][j] += min(g[v[i]], f[v[i]][j]);
}
for (int i = a[u]; i <= a[u] + d && i <= 5000; ++i)
g[u] = min(g[u], f[u][i] + 1);
}
int main() {
freopen("big.in", "r", stdin);
freopen("big.out", "w", stdout);
scanf("%d%d", &d, &n);
memset(f, 0x3f, sizeof f);
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
add_edge(u, v), add_edge(v, u);
}
dfs(1, 0);
printf("%d\n", g[1]);
fclose(stdin);
fclose(stdout);
return 0;
}
时间复杂度 $O(n^2)$