POJ 3723 Conscription (Kruskal并查集求最小生成树)

Conscription
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 14661   Accepted: 5102

Description

Windy has a country, and he wants to build an army to protect his country. He has picked up N girls and M boys and wants to collect them to be his soldiers. To collect a soldier without any privilege, he must pay 10000 RMB. There are some relationships between girls and boys and Windy can use these relationships to reduce his cost. If girl x and boy y have a relationship d and one of them has been collected, Windy can collect the other one with 10000-d RMB. Now given all the relationships between girls and boys, your assignment is to find the least amount of money Windy has to pay. Notice that only one relationship can be used when collecting one soldier.

Input

The first line of input is the number of test case.
The first line of each test case contains three integers, NM and R.
Then R lines followed, each contains three integers xiyi and di.
There is a blank line before each test case.

1 ≤ NM ≤ 10000
0 ≤ R ≤ 50,000
0 ≤ xi < N
0 ≤ yi < M
0 < di < 10000

Output

For each test case output the answer in a single line.

Sample Input

2

5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 3 2049
4 2 2104
2 2 781

5 5 10
2 4 9820
3 2 6236
3 1 8864
2 4 8326
2 0 5156
2 0 1463
4 1 2439
0 4 4373
3 4 8889
2 4 3133

Sample Output

71071
54223




题意:有N+M个顶点,边:两个顶点之间的亲密度。
    单独获取某个顶点的花费是10000;
    如果两个顶点的亲密度为x,我已经获取了其中一个顶点,那么获取另一个顶点花费10000-x。   
现在你要把所有的顶点全部获取,算出最小花费。




【方法1】
我们可以想到这是一个森林,我们需要求最小生成树(最小花费)。
由于权值x越大花费越小,所以我们把权值取反为-x,求出最小生成树(通过关系获取比直接获取可以少花费多少),
然后再加上每个点花费的10000,就得到了我们的总花费。



【方法2】
你可能会说,直接把权值设置为10000-x也可以写,
但是考虑到我们求出最小生成树之后还要再求出连通分量,
总花费 = 最小生成树(通过关系获取)+ 连通分量*10000。

明显比前一种算法复杂度要高(主要是我还不会写连通分量)。

不过不会写不要紧,我们可以用Kruskal算法用到的并查集,
并查集中的parent数组保存的就是属于哪棵树,
所以算一下parent中有几个不同的值就可以得到连通分量。



Kruskal算法讲解:http://www.cnblogs.com/zhangjiuding/p/7796526.html


代码:
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <vector>
#include <set>
#include <queue> 
using namespace std;
typedef long long ll;
#define MAX_N 20010
#define MAX_E 50010
#define INF 2147483647

struct edge {
    int u,v,cost;
}; 

bool cmp(const edge & e1,const edge &e2){
    return e1.cost < e2.cost;
}

edge es[MAX_E];  //边集 
int V,E;   //顶点数和边数 

int par[MAX_N]; //par[i]表示i节点的父节点 
int rank[MAX_N]; // 树的高度 

//初始化n个元素
void init(int n){
    for(int i = 0;i < n; i++){
        par[i] = i;
        rank[i] = 0;
    }
} 

//查询包含x节点的树的根 
int find(int x){
    if(par[x] == x) return x;
    else return par[x] = find(par[x]); 
}

//合并 x和y所属的集合 
void unite(int x,int y){
    x = find(x);
    y = find(y);
    if(x == y) return;
    
    if(rank[x] < rank[y]){
        par[x] = y; 
    }else{
        par[y] = x;
        if(rank[x] == rank[y]) rank[x]++;
    }
}

//判断x和y是否属于同一个集合 
bool same(int x,int y){
    return find(x) == find(y);
}


int kruskal(){
  sort(es, es + E, cmp);
  init(V);
  int res = 0;
  for(int i = 0;i < E; i++ ){
      edge e = es[i];
      if(!same(e.u,e.v)){
          unite(e.u, e.v);
          res += e.cost;
        }
    }
    return res;
} 

int main(){
    int t;
    cin >> t;
    while(t--){
    
        int n,m,r;
        cin >> n >> m >> r;
        V = n+m;
        E = r;
        for(int i = 0;i < r; i++){
            edge e;
            scanf("%d%d%d",&e.u,&e.v,&e.cost);
            e.cost = - e.cost;
            e.v += n;
            es[i] = e;
        }


      cout << V*10000 + kruskal() << endl;
  }
    return 0;
} 


 
原文地址:https://www.cnblogs.com/zhangjiuding/p/7800805.html