P2458 [SDOI2006]保安站岗 树形dp最小费用覆盖点

传送门:https://www.luogu.com.cn/problem/P2458

题意

给一棵树,在树上安置保安,一条边的两个端点至少安置一个保安,保安都有费用。 问符合情况的最小费用是多少。

思路

因为一条边的两个端点必须要有一个,可以有两个,所以这是不限制父亲和儿子关系的。

如果一条边的两个端点都选,那一定不会是最小费用,所以只需要选一点即可。

对于一个点选不选,对父亲和儿子选不选都有影响,所以设f[u][0/1/2]。 f[u][]表示以u为根的子树。

  • f[u][0]表示u自己选
  • f[u][1]表示自己不选,儿子选
  • f[u][2]表示自己不选,父亲选

转移方程:

自己选,不妨碍其他点的选择,即

  • $f[u][0]+=\sum_{v\in g[u]}min(f[v][0],f[v][1],f[v][2])$

父亲选,所以自己不用选了,只需要加上儿子的最小费用,即

  • $f[u][2]+=\sum_{v\in g[u]} min(f[v][0]+f[v][1])$

儿子选,对于所有儿子,肯定不会全部都选,选择最小的儿子即可,即

  • $f[u][1]+=\sum_{v\in g[u]除最小}f[v][1]+f[v_{最优}][0]$

对于f[u][1],我们怎么找到最小的呢?

记录$mn=min(mn,f[v][0]-f[v][1])$

1
2
3
4
5
6
7
8
9
10
11
bool flag = 1;
if(f[v][0] <= f[v][1]) {
flag = 0;
f[u][1] += f[v][0];
}
else {
f[u][1] += f[v][1];
mn = min(mn, f[v][0] - f[v][1]);
}
}
if(flag) f[u][1] += mn;

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
##include "bits/stdc++.h"
using namespace std;

const int N = 1e4 + 10;
vector<int> g[N];
int val[N];
int f[N][3];

void dfs(int u, int fa) {
f[u][0] = val[u];
bool flag = 1;
int mn = 1e9;
for(auto v : g[u]) {
if(v == fa) continue;
dfs(v, u);
f[u][0] += min(f[v][0], min(f[v][1], f[v][2]));
f[u][2] += min(f[v][0], f[v][1]);
if(f[v][0] <= f[v][1]) {
flag = 0;
f[u][1] += f[v][0];
}
else {
f[u][1] += f[v][1];
mn = min(mn, f[v][0] - f[v][1]);
}
}
if(flag) f[u][1] += mn;
}

void solve() {
int n; cin >> n;
for(int i = 1;i <= n; i++) {
int id, m; cin >> id >> val[id] >> m;
for(int j = 1;j <= m; j++) {
int v; cin >> v;
g[v].eb(id);
g[id].eb(v);
}
}
dfs(1, -1);
cout << min(f[1][0], f[1][1]) << endl;
}

signed main() {
solve();
}

本文作者:jujimeizuo
本文地址https://blog.jujimeizuo.cn/2021/03/25/p2458-sdoi2006/
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0 协议。转载请注明出处!