树上最小点覆盖的一类问题
前言
关于下文中 \(lim\) 较小的最小点覆盖问题,我们通常会对每个节点设出若干状态转移,而下文所说的问题是此问题的通解,但复杂度为平方级别
题意
给定一棵无根有权树,每个点建消防站都有一定代价 \(c\),每个点都有一个限制 \(lim\),表示离它最近的消防站的最大距离。求让所有点安全的最小代价。
分析
我们首先考虑对于一个点最在乎的是什么,发现其实是离他最近的且建设消防站的点。
既然这个状态如此重要,而且似乎直接表示出来非常的困难。我们考虑直接将其加入状态里。
设置动态规划状态
设 \(dp_{u,i}\) 为 \(u\) 这个点离他最近的且建设消防站的点为 \(i\) 且 \(u\) 子树内结点全部安全。
转移
在树形 \(dp\) 的转移时,我们通常用父亲与儿子的关系去转移,我们在这里也是一样的。
-
若 \(u,v\) 两点最近点不相同考虑直接转移即可。
-
若 \(u,v\) 两点最近点相同,发现会重复统计一次贡献,减去即可。
其中第 1 中转移开个 \(min\) 数组优化即可。
代码
void dfs1(int u, int par) {
// Debug(u);
for (auto [v, w] : G[u]) {
if (v == par)
continue;
dis[v] = dis[u] + w;
dfs1(v, u);
}
}
void dfs(int u, int par) {
// Debug(u);
for (int i = 1; i <= n; i++) {
if (can[u][i])
dp[u][i] = c[i];
else
dp[u][i] = inf;
}
for (auto [v, w] : G[u]) {
if (v == par)
continue;
dfs(v, u);
for (int i = 1; i <= n; i++)
dp[u][i] += min(f[v], dp[v][i] - c[i]);
}
f[u] = *min_element(dp[u] + 1, dp[u] + n + 1);
}
void init(int n) {
for (int i = 1; i <= n; i++) {
G[i].clear();
for(int j=1;j<=n;j++)
can[i][j]=0;
}
}
void Main() {
n = rd;
init(n);
for (int i = 1; i <= n; i++)
c[i] = rd;
for (int i = 1; i <= n; i++)
d[i] = rd;
for (int i = 1; i < n; i++) {
int u = rd, v = rd, w = rd;
G[u].pb({v, w});
G[v].pb({u, w});
}
for (int i = 1; i <= n; i++) {
dis[i] = 0, dfs1(i, 0);
for (int j = 1; j <= n; j++)
if (dis[j] <= d[i])
can[i][j] = 1;
}
dfs(1, 0);
cout << f[1] << endl;
}