P2700 逐个击破
题意:
给定一个有边权的无向图,边数为 n-1,删边代价为边权。求使得 k 个指定点(称为 “坏点”)两两不连通的最小代价
思路:
法一:类似 kruskal
考虑最大化要保留的边的边权和。
所有边按边权从大到小排序。当两个连通块中都有坏点时不能连边,否则就能连边
const signed N = 1e5 + 10;
int n, k, p[N]; ll ans;
bool bad[N];
int get(int x) {
return x == p[x] ? x : p[x] = get(p[x]);
}
void sol() {
cin >> n >> k;
while(k--) {
int x; cin >> x, bad[x] = true;
}
vector<array<int, 3>> edges(n-1);
for(auto &[u,v,w]: edges) cin >> u >> v >> w, ans += w;
sort(all(edges), [](array<int,3> A, array<int,3> B) {
return A[2] > B[2];
});
iota(p, p + N, 0);
for(auto [u,v,w]: edges) { //选择最后要保留的边
u = get(u), v = get(v);
if(bad[u] && bad[v]) continue;
ans -= w, p[u] = v, bad[v] |= bad[u]; //把整个连通块都标记为坏的
}
cout << ans << '\n';
}
法二:树形dp
(测了一下发现所有点都是连通的,所以实际上这个图是一棵树。。。)
\(f[u][0/1]\) 表示当前 \(u\) 所在的连通块中无/有一个坏点
#define INF 1e12
const signed N = 1e5 + 10;
int n, k; bool bad[N]; vector<PII> G[N];
ll f[N][2];
void dfs(int u, int fa) {
if(bad[u]) {
f[u][1] = 0;
for(auto [v,w]: G[u]) if(v != fa)
dfs(v, u), f[u][1] += min(f[v][0], f[v][1] + w);
} else {
f[u][0] = 0;
for(auto [v,w]: G[u]) if(v != fa) {
dfs(v, u);
f[u][0] += min(f[v][1] + w, f[v][0]);
f[u][1] = min(f[u][1], f[v][1] - min(f[v][0], f[v][1] + w));
}
for(auto [v,w]: G[u]) if(v != fa)
f[u][1] += min(f[v][0], f[v][1] + w);
}
}
void sol() {
cin >> n >> k;
while(k--) {
int x; cin >> x; bad[x] = true;
}
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
G[u].push_back({v, w}), G[v].push_back({u, w});
}
for(int i = 0; i < n; i++) f[i][0] = f[i][1] = INF;
dfs(0, -1);
cout << min(f[0][0], f[0][1]);
}

浙公网安备 33010602011771号