【克鲁斯卡尔蒜法-最小生成树算法】-zzuli-2271 -Problem -E-魔法交流活动

问题 E: 魔法交流活动

题目描述

魔法学校近日开展了主题为“天气晴朗”的魔法交流活动。
N名魔法师按阵法站好,之后选取N - 1条魔法链将所有魔法师的魔力连接起来,形成一个魔法阵。
魔法链是做法成功与否的关键。每一条魔法链都有一个魔力值V,魔法最终的效果取决于阵中所有魔法链的魔力值的和。
由于逆天改命的魔法过于暴力,所以我们要求阵中的魔法链的魔力值最大值尽可能的小,与此同时,魔力值之和要尽可能的大。
现在给定魔法师人数N,魔法链数目M。求此魔法阵的最大效果。

输入

两个正整数N,M。(1 <= N <= 10^5, N <= M <= 2 * 10^5) 
接下来M行,每一行有三个整数A, B, V。(1 <= A, B <= N, INT_MIN <= V <= INT_MAX) 
保证输入数据合法。

输出

输出一个正整数R,表示符合条件的魔法阵的魔力值之和。

样例输入

4 6
1 2 3
1 3 1
1 4 7
2 3 4
2 4 5
3 4 6

样例输出

12
-

大致题意分析:

   有n个点,选取n-1条边把他们全部连起来形成一棵树,每条边都有一个权值;要求1:所有的边的权值的最大值最小,然后还又要求2:这棵树中的所有的边权值的和最大。

  思路:这个题目是对边来进行筛选的,需要用克鲁斯卡尔蒜法。这个蒜法是对边来进行操作的,按照边来逐渐形成整棵最小生成树。

                 具体需要跑两边,第一遍从小到大对边进行一次筛选,筛选出的边的集合可以构成一颗树即可;这是最后一条新加进来的边就是最大的权值maxmin(要求1达成)。

    第二遍,从大到小,具体从等于最小的最大边权值maxlen的边开始跑一遍,直到筛选出的边的集合可以构成一颗树即可,进行筛选、求和,即为最终结果。


ac代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <iostream>
  4 #include <algorithm>
  5 #include <queue>
  6 #include <stack>
  7 #include <vector>
  8 #include<math.h>
  9 #include <string.h>
 10 #include<set>
 11 using namespace std;
 12 #define inf 0x7fffffff
 13 #define maxn 10000000
 14 const double pi=acos(-1.0);
 15 #define ll long long
 16 #define N 100008
 17 
 18 int n,m;
 19 struct Edge//存边的结构体
 20 {
 21     int a,b;//两个顶点的编号
 22     ll dis;
 23     Edge(int a=0,int b=0,ll dis=0):a(a),b(b),dis(dis) {}
 24 } edge[N*2];
 25 
 26 int root[N];
 27 
 28 int findroot(int a) //返回点a的根节点,并查集
 29 {
 30     if(root[a]==a)
 31         return a;
 32     return root[a]=findroot(root[a]);
 33 }
 34 int unite(int a,int b) //将a和b节点用并查集的思路连接到一起
 35 {
 36     if(a==b)return 0;//在联通块内部加上的边,及a-b出现在同一个集合内部了
 37     else
 38     {
 39         root[a]=b;
 40         return 1;
 41     }
 42 }
 43 bool cmp(Edge x,Edge y)
 44 {
 45     return x.dis<y.dis;
 46 }
 47 int fact1(int n) //kruscal蒜法调用1次,计算出最小的最大值maxlen
 48 {
 49     for(int i=1; i<=n; i++)
 50         root[i]=i;
 51 
 52     int cnt=0;
 53     ll ans=(ll)inf*(-1);
 54     int i=1;
 55     while(cnt<n-1)//从小到大
 56     {
 57         if(unite(findroot(edge[i].a),findroot(edge[i].b))==1 )
 58         {
 59             cnt++;
 60             ans=max(ans,edge[i].dis);
 61         }
 62         i++;
 63     }
 64     return ans;
 65 }
 66 ll fact2(int n,int maxlen) //kruscal蒜法调用2次,求出在manlen范围下的最小生成树的边集的和
 67 {
 68     for(int i=1; i<=n; i++)
 69         root[i]=i;
 70     int cnt=0;
 71     ll ans=0;
 72     int i=m;
 73     while(cnt<n-1)//从大到小跑边
 74     {
 75         if(edge[i].dis<=(ll)maxlen && unite(findroot(edge[i].a),findroot(edge[i].b))==1 )
 76         {
 77             cnt++;//统计不形成回路的边数,等于n-1时跳出循环
 78             ans+=edge[i].dis;
 79         }
 80         i--;
 81     }
 82     return ans;
 83 }
 84 int main()
 85 {
 86     while(scanf("%d%d",&n,&m)!=EOF)
 87     {
 88 
 89         int x,y;
 90         ll v;
 91         for(int i=1; i<=m; i++)
 92         {
 93             scanf("%d%d%lld",&x,&y,&v);
 94             edge[i]=Edge(x,y,v);//不习惯这种方式可以手动逐一赋值!
 95 
 96         }
 97         sort(edge+1,edge+1+m,cmp);//按边权值升序排列
 98 
 99         int maxlen=fact1(n);//找到合法的最大长度
100 
101         printf("%lld
",fact2(n,maxlen));//求出最终结果
102     }
103 
104     return 0;
105 }
View Code