洛谷题单指南-最小生成树-P2700 逐个击破
原题链接:https://www.luogu.com.cn/problem/P2700
题意解读:n个节点n-1条边的带权图,其中有k个节点标记为敌军,删掉一些边,使得k个敌军节点互补连通,求删除的边权值之和最小值。
解题思路:
1、问题分析
换一个角度,要求删除多少边使得k个节点互不连通,不如在一个图中挑选一些边,使得形成k个连通块,并且k个敌军节点互不连通,剩下
的边就是要删除的边,要让剩下的边最小,必须让挑选的边最大。
2、算法思路
挑选尽可能大的边,使得图中形成k个连通块,且已标识为敌军的k个节点互相不连通,借助于最大生成树的思想,加上并查集处理即可解决。
将k个敌军用hash数组保存,h[i]=true表示节点i是敌军节点
从大到小处理边,对于每一条边u->v,找到u所在连通块的并查集根节点a,v所在连通块的并查集根节点b
如果a和b都是敌军节点,则不允许将a、b连通
如果a和b有一个是敌军节点,则可以将其连通,并查集合并时将敌军节点作为根,累加已挑选边
如果a和b都不是敌军节点,则可以将其连通,累加已挑选边
用总的边权和-已挑选边权和即得到答案
100分代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100005;
struct Edge
{
int u, v, w;
bool operator < (const Edge &e) const
{
return w > e.w; //权值从大到小处理
}
} edges[N];
int p[N];
bool h[N]; //标识敌军的位置
int n, k;
LL sum1, sum2; //sum1是所有权值之和,sum2是不会连通k个敌军的权值之和
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> k;
for(int i = 1; i <= k; i++)
{
int x;
cin >> x;
h[x] = true;
}
for(int i = 1; i <= n; i++) p[i] = i;
for(int i = 1; i < n; i++)
{
cin >> edges[i].u >> edges[i].v >> edges[i].w;
sum1 += edges[i].w;
}
sort(edges + 1, edges + n);
int cnt = n;
for(int i = 1; i < n; i++)
{
int a = find(edges[i].u);
int b = find(edges[i].v);
if(h[a] && h[b]) continue; //如果边的两点所在连通块的根节点都是敌军位置,不能连通
if(h[a]) p[b] = a; //用已标识敌军的节点a作为根节点
else p[a] = b; //用已标识敌军的节点b作为根节点,或者b也不是敌军节点就直接连通
sum2 += edges[i].w;
if(--cnt == k) break;
}
cout << sum1 - sum2 << endl;
return 0;
}