1. 程式人生 > >HDU 1102 Constructing Roads(kruskal)

HDU 1102 Constructing Roads(kruskal)

\n enter i++ () 技術 結構 conn names 計算

Constructing Roads

  There are N villages, which are numbered from 1 to N, and you should build some roads such that every two villages can connect to each other. We say two village A and B are connected, if and only if there is a road between A and B, or there exists a village C such that there is a road between A and C, and C and B are connected.

  We know that there are already some roads between some villages and your job is the build some roads such that all the villages are connect and the length of all the roads built is minimum.

Input

  The first line is an integer N (3 <= N <= 100), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 1000]) between village i and village j.

Then there is an integer Q (0 <= Q <= N * (N + 1) / 2). Then come Q lines, each line contains two integers a and b (1 <= a < b <= N), which means the road between village a and village b has been built.
Output

  You should output a line contains an integer, which is the length of all the roads to be built such that all the villages are connected, and this value is minimum.
Sample Input

3
0 990 692
990 0 179
692 179 0
1
1 2

Sample Output

179

解題思路:
  本題有多組測試數據,每種數據第一行給出村子的數量n,跟隨n行,每行為使該行對應的村子與其他村子聯通所需要修築的道路距離(其實就是所有村子的鄰接矩陣),之後給出已經修好的道路數量q,之後q行跟隨,每行包括兩個整數分別為道路兩端的兩個村子。要求輸出使所有村子聯通還要修築道路的最小長度。

  若不看已經修好的道路,本題就是一個最小生成樹問題。在這裏使用kruskal算法。

  kruskal算法核心思想:  

  既然已經給出了鄰接矩陣,那我們可以將其拆分為鄰接表,即將每一條可以修築的道路都記錄下來。初始視所有結點都為不連通,之後將道路按長度排序,從小到大枚舉所有邊,判斷邊的兩個頂點是否已經連通,若已經連通不做處理,若不連通則將該邊記錄入最小生成樹,並記錄當前總權值,最小生成樹也是樹,符合邊數等於頂點數減一,所以結束條件為邊數等於定點數減一,如果邊數不等於頂點數減一則說明圖不連通(當然在這裏不存在不連通的情況,不過寫上一定不會錯,還能節約時間)。

  將鄰接矩陣拆分為鄰接表:用結構edge保存道路,其成員包括兩個頂點村子node1,node2與道路長度len。

技術分享圖片
for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                Edge[cnt].node1 = i;
                Edge[cnt].node2 = j;
                scanf("%d", &Edge[cnt].len);
                cnt++;
            }
}
拆分鄰接矩陣

  在判斷是否連通使用並查集

技術分享圖片
int father[maxn];   //記錄父結點
int getFather(int x){
    int tempx = x;
    while(x != father[x]){  //尋找父結點
        x = father[x];
    }
    while(tempx != father[tempx]){  //將路徑上所有的點的father值改為父結點
        int preTempx = tempx;
        tempx = father[tempx];
        father[preTempx] = tempx;
    }
    return x;
}
並查集

  kruskal算法

技術分享圖片
int kruskal(int n, int m){  //傳入頂點數與邊數
    int ans = 0, edgeCnt = 0;
    //ans記錄道路長度和,edgeCnt記錄當前最小生成樹中邊的數量
    for(int i = 0; i < n; i++){
        father[i] = i;
    }
    sort(Edge, Edge + m, cmp);  //將邊排序
    for(int i = 0; i < m; i++){ //從小到大枚舉所有邊
        int faNode1 = getFather(Edge[i].node1);
        int faNode2 = getFather(Edge[i].node2);
        if(faNode1 != faNode2){ //判斷該邊的兩個頂點是否已經連通
            father[faNode1] = faNode2;  //不連通將其標記為連通
            ans += Edge[i].len;    //記錄長度
            edgeCnt++;  //記錄遍數
            if(edgeCnt == n - 1)    //邊數等於頂點數減一
                break;
        }
    }
    if(edgeCnt == n - 1){   //連通
        return ans;
    }else{  //不連通
        return -1;
    }
}
kruskal

  之後就要考慮已經建好的道路,這其實很簡單,只需要將建好的道路長度標記為0即可。給定一個已經建好的道路數量q,之後傳入q組數據,每組包含兩個村子village1與village2,根據我們鄰接矩陣的拆分方法,我們可以得知,在記錄邊的數組Edge中,village1與village2所對應邊的下標為village1 * n + village2(道路是雙向的,在Edge中會有兩個頂點為village1 與 village2的道路,但因為我們計算時會排序,所以標記一個就好)。

AC代碼

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 1e4+10;
 4 struct edge{
 5     int node1, node2;
 6     int len;
 7 }Edge[maxn];
 8 bool cmp(edge e1, edge e2){
 9     return e1.len < e2.len;
10 }
11 int father[maxn];   //記錄父結點
12 int getFather(int x){
13     int tempx = x;
14     while(x != father[x]){  //尋找父結點
15         x = father[x];
16     }
17     while(tempx != father[tempx]){  //將路徑上所有的點的father值改為父結點
18         int preTempx = tempx;
19         tempx = father[tempx];
20         father[preTempx] = tempx;
21     }
22     return x;
23 }
24 
25 int kruskal(int n, int m){  //傳入頂點數與邊數
26     int ans = 0, edgeCnt = 0;
27     //ans記錄道路長度和,edgeCnt記錄當前最小生成樹中邊的數量
28     for(int i = 0; i < n; i++){
29         father[i] = i;
30     }
31     sort(Edge, Edge + m, cmp);  //將邊排序
32     for(int i = 0; i < m; i++){ //從小到大枚舉所有邊
33         int faNode1 = getFather(Edge[i].node1);
34         int faNode2 = getFather(Edge[i].node2);
35         if(faNode1 != faNode2){ //判斷該邊的兩個頂點是否已經連通
36             father[faNode1] = faNode2;  //不連通將其標記為連通
37             ans += Edge[i].len;    //記錄長度
38             edgeCnt++;  //記錄遍數
39             if(edgeCnt == n - 1)    //邊數等於頂點數減一
40                 break;
41         }
42     }
43     if(edgeCnt == n - 1){   //連通
44         return ans;
45     }else{  //不連通
46         return -1;
47     }
48 }
49 int main()
50 {
51     int n, cnt = 0;
52     while(scanf("%d", &n) != EOF){
53         cnt = 0;    //cnt記錄邊數
54         int numNode = n, numEdge = 0;   //numNode記錄村子數量,numEdge記錄總道路數量
55         for(int i = 0; i < n; i++){ //拆分鄰接矩陣
56             for(int j = 0; j < n; j++){
57                 Edge[cnt].node1 = i;
58                 Edge[cnt].node2 = j;
59                 scanf("%d", &Edge[cnt].len);
60                 cnt++;
61             }
62         }
63         numEdge = cnt;
64         int q;
65         scanf("%d", &q);
66         for(int i = 0; i < q; i++){
67             int village1, village2; //輸入已經存在道路的兩個村子
68             scanf("%d%d", &village1, &village2);
69             village1--; //由於之前拆分時 i 與 j從0開始所以村子對應的值為輸入的值減一
70             village2--;
71             Edge[village1 * n + village2].len = 0;
72         }
73         int ans = kruskal(numNode, numEdge);
74         printf("%d\n", ans);
75     }
76     return 0;
77 }

HDU 1102 Constructing Roads(kruskal)