LuoguP2700逐个击破【并查集/生成树/正难则反】By cellur925

题目传送门

题目大意:给你一棵树,求把其中k个点相互隔离(不连通)所需要的边权代价。


这题我开始是想要求出把k个点联通的最小代价的,但后来发现还是实现起来比较困难,题解里貌似也没有这种做法,于是就鸽了。但是大体的思考方向还是不直接去想把k个点隔离,而是把问题转化。

花费最小代价删边->花费最大代价建边。而建边的时候如果遇到一条两边都是敌人的边,我们显然是不需要建的,所以这其实我们需要维护敌人的网络,用并查集来维护。

首先我们标记敌人点,再把边从大到小排序。枚举所有的边,如果它两端点都是敌人,那肯定不连他。如果两端点有一个是敌人,也连上,并在那个无辜的点上打一个标记,因为之后的点也不能再连他。于是我们就可以用并查集维护。最后注意我们之后调用的都是这个集合的代表元,即getf。

Code

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define maxn 100090
 4 
 5 using namespace std;
 6 typedef long long ll;
 7 
 8 int n,k;
 9 ll ans;
10 int vis[maxn],fa[maxn];
11 struct node{
12     int to,from,val;
13 }edge[maxn*2];
14 
15 bool cmp(node a,node b)
16 {
17     return a.val>b.val;
18 }
19 
20 int getf(int x)
21 {
22     if(fa[x]==x) return x;
23     return getf(fa[x]);
24 }
25 
26 int main()
27 {
28     scanf("%d%d",&n,&k);
29     for(int i=1;i<=k;i++)
30     {
31         int x=0;
32         scanf("%d",&x);
33         vis[x]=1;
34     }
35     for(int i=1;i<=n-1;i++)
36         scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].val),ans+=edge[i].val;
37     for(int i=1;i<=n;i++) fa[i]=i;
38     sort(edge+1,edge+1+n-1,cmp);
39     for(int i=1;i<=n-1;i++)
40     {
41         int pp=getf(edge[i].from),qq=getf(edge[i].to);
42         if(vis[pp]&&vis[qq]) continue;
43         ans-=edge[i].val;
44         if(qq!=pp) fa[qq]=pp;
45         if(vis[pp]) vis[qq]=1;
46         else if(vis[qq]) vis[pp]=1;
47     }
48     printf("%lld",ans);
49     return 0;
50 }
View Code
原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9744089.html