侦察守卫
给与n,d。给与n的整数表示每个节点放侦察兵的价钱。每个侦察兵可以看到d远。
给与m, 接下来有m个节点表示有人。
接下来给与n-1行表示树的边。
求看到所有人的最佳价钱。(n<=5e5, d<=20)
解:复杂度 O(nd)
我们设计两个数组 f 和 g :
令f [ i ][ j ] 为当我们还没看到以 i 为子树的接下来 j 深, 但是以下的都已经涂了 + 往上涂 j 高的最价钱
令g [ i ][ j ] 为当我们还没看到以 i 为子树已经涂满 + 往上涂 j 高的最价钱
有点复杂那么画个图吧:
如果现在考虑u和v, u为v的父亲
初始化的状态为:
若u有人: f[u][0]=g[u][0]=cost[u]
g[u][j]=cost[u] j->[1,d]
g[u][d+1]=inf
而答案显而易见就是 g[1][0]
转移状态有点复杂:
那么 g[u][j] = min( g[u][j]+ f[v][j], g[v][j+1] + f[u][j+1] );
f[u][0]=g[u][0]应该蛮明显的嘛, 毕竟往上走0步就是一样的
而 f[u][j] += f [v][j-1]
不过我们这里就需要对g[u][]取后缀最小值,f[u][]取前缀最小值
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf = 1e16, N = 5e5 + 500;
int g[N][22], f[N][22];
signed main() {
ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n, d; cin >> n >> d;
vector<vector<int>> adj(n + 1);
vector<int> c(n + 1), V(n + 1);
for (int i = 1; i <= n; i++) cin >> c[i];
int m; cin >> m; for (int i = 1; i <= m; i++) { int x; cin >> x; V[x] = 1; }
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
function<void(int, int)> dfs = [&](int u, int fa) {
if (V[u]) g[u][0] = f[u][0] = c[u];
for (int i = 1; i <= d; i++) g[u][i] = c[u];
g[u][d + 1] = inf;
for (auto& v : adj[u]) {
if (v == fa) continue;
dfs(v, u);
for (int i = d; i >= 0; i--) g[u][i] = min(g[u][i] + f[v][i], g[v][i + 1] + f[u][i + 1]);
for (int i = d; i >= 0; i--) g[u][i] = min(g[u][i], g[u][i + 1]);
f[u][0] = g[u][0];
for (int i = 1; i <= d + 1; i++) f[u][i] += f[v][i - 1];
for (int i = 1; i <= d + 1; i++) f[u][i] = min(f[u][i], f[u][i - 1]);
}
};
dfs(1, 0);
cout << g[1][0] << '\n';
}
//nyl,加油!