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]);
}
posted @ 2022-09-16 10:29  Bellala  阅读(93)  评论(0)    收藏  举报