最大僞森林——kruskal算法活用 (HDU - 3367)

最大僞森林——kruskal算法活用 (HDU - 3367)

  • kruskal這一用來求生成樹的算法,通過修改拓展以後,能夠求不少種形式的子圖,本題(HDU3367)即爲一個應用案例

單擊進入原題php

  • 如下是原題內容

Pseudoforest
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 4058 Accepted Submission(s): 1615ios

Problem Description
In graph theory, a pseudoforest is an undirected graph in which every connected component has at most one cycle. The maximal pseudoforests of G are the pseudoforest subgraphs of G that are not contained within any larger pseudoforest of G. A pesudoforest is larger than another if and only if the total value of the edges is greater than another one’s.算法

Input
The input consists of multiple test cases. The first line of each test case contains two integers, n(0 < n <= 10000), m(0 <= m <= 100000), which are the number of the vertexes and the number of the edges. The next m lines, each line consists of three integers, u, v, c, which means there is an edge with value c (0 < c <= 10000) between u and v. You can assume that there are no loop and no multiple edges.
The last test case is followed by a line containing two zeros, which means the end of the input.數組

Output
Output the sum of the value of the edges of the maximum pesudoforest.oop

Sample Input
3 3
0 1 1
1 2 1
2 0 1
4 5
0 1 1
1 2 1
2 3 1
3 0 1
0 2 2
0 0spa

Sample Output
3
5設計

Source
「光庭杯」第五屆華中北區程序設計邀請賽 暨 WHU第八屆程序設計競賽rest

Recommend
lcycode

題意

  • 本題介紹了一個「僞森林」的概念:

    component

    • 對一個無向圖,若是它的一個子圖知足每一個連通份量內部至多有一個環,那麼這一個子圖就是該無向圖的僞森林。

    • 類比於最大生成樹,若是在這個無向圖中,某個僞森林所具備的邊權之和大於等於全部此無向圖的僞森林,那麼該僞森林就叫作最大僞森林。
  • 給咱們一個無向圖,要求咱們求出它的最大僞森林所具備的邊權之和。

思路

  • 先思考若是每一個連通份量沒有環的狀況,此時也就是求最大生成森林,此時因爲並查集的重要做用,因此用kruskal算法很是合適

  • 如今這個生成森林可讓每棵樹至多存在一個環,顯然咱們的結果應該比簡單的生成森林還要大一些。可是咱們這樣放棄kruskal算法是惋惜的,咱們能夠對它進行修改來求出這個最大僞森林。

咱們可能很容易想到,在kruskal算法算得最大生成森林以後,再把全部的邊掃一遍,把上一次沒有被選中的邊(毫無疑問,這條邊必定會構成一個環)再加入森林,同時經過並查集的祖先找到它所在的那棵樹,標記這顆樹爲「有環」,這樣以後每次判斷沒有環纔會添加,就不會出現兩個環了

  • 我的感受上面我這個本身想出來的方法很具備迷惑性,由於因爲邊已經排好了序,容易讓人認爲沒有更優狀況了。不過仔細推敲就能發現反例,以下圖:

它的最大生成樹是什麼?(也就是說,咱們第一步作完以後,獲得的結果是什麼?毫無疑問是下圖:

那以後咱們第二步加邊以後 他會變成以下的一個僞森林:

這是最大僞森林? 不是,下面這個纔是:

這就否認了咱們的想法

  • 顯然咱們的設想是片面的,若是進行一次kruskal的最大生成森林,那麼咱們能夠知道,出現的圖都是「儘量連通的」,也就是爲了連通性犧牲了總權值。

可是最大僞森林不須要這樣的犧牲,它徹底能夠去掉一條邊來生成兩棵樹,進而選擇到邊權更大的邊,只要每棵樹不超過一個環,也就是能夠犧牲一次連通性來換取更大的邊權。

改進思路

  • 注意咱們在上面提到過「能夠犧牲一次連通性來換取更大的邊權。」,其實這個過程咱們徹底能夠在kruskal算法的內部修改實現:

    • 咱們考慮把每棵樹的有環狀況實時記錄,使用bk2數組,bk2[i]爲true 表示並查集祖先爲i的樹目前有環。

    • 仍然是邊排序以後從頭進行掃描,每次先路徑壓縮找到兩個點所在樹的祖先,而後經過bk2數組得知這兩棵樹的有無環狀況。若是:

      1.都是false無環
      那麼顯然這條邊能夠被選中加入森林(不會違背任何條件)
      要注意:若是這兩棵樹是同一顆,那麼要標記這棵樹爲有環

      2.兩棵都有環
      那麼不能夠加入這條邊,不然將會使生成的一棵樹中有兩個環

      3.一棵有環一棵無環
      那麼根據僞森林的定義,咱們能夠把這條邊選入,可是要注意在以後把
      要注意:若是這兩棵樹是同一棵,那麼不能夠加入(但事實上不可能出現這種狀況,不然標記就是錯誤的)


- 其實實現上述邏輯以後,咱們就已經成功地應用了僞森林的定義,結果即爲最大僞森林的邊權之和

AC代碼

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m, x, y, z, co = 1;
long long sum = 0;

struct ab					//邊 
{
    int u, v, w;
} aa[200005];

bool bk2[10005] = {0};		//某棵樹有無環 

int f[10005] = {0};			//並查集 

bool cmp(ab a, ab b)
{
    return a.w > b.w;
}

int fd(int xx)				//路徑壓縮 
{
    if (f[xx] != xx)
    {
        f[xx] = fd(f[xx]);
    }
    return f[xx];
}

int main()
{
	while (1)
	{
	    scanf("%d%d", &n, &m);
	    if (!n && !m)
	    {
	    	break;
		}
		
		memset(bk2, 0, sizeof(bk2));
		
	    for (int i = 1; i <= m; i++)
	    {
	        scanf("%d%d%d", &x, &y, &z);
	        aa[i].u = x;
	        aa[i].v = y;
	        aa[i].w = z;
	    }
	    
	    for (int i = 0; i <= n; i++)
	    {
	        f[i] = i;
	    }
	    
	    sort(aa + 1, aa + m + 1, cmp);
	    
	    sum = 0;
	    for (int i = 1; i <= m; i++)		//生成最大僞森林 
	    {
	        if (fd(aa[i].u) == fd(aa[i].v))
	        {
	        	if (bk2[f[aa[i].u]] || bk2[f[aa[i].v]])
	        	{
	        		continue;
				}
	        	else
	        	{
	        		bk2[f[aa[i].u]] = bk2[f[aa[i].v]] = true;
	        		sum += aa[i].w;
				}
	        }
	        else
	        {
	        	if (bk2[f[aa[i].u]] && bk2[f[aa[i].v]])
	        	{
	        		continue;
				}
	        	sum += aa[i].w;
	        	if (bk2[f[aa[i].u]] || bk2[f[aa[i].v]])
	        	{
	        		bk2[f[aa[i].u]] = true;
		        	bk2[f[aa[i].v]] = true;
				}
		        f[f[aa[i].v]] = f[aa[i].u];
			}
	    }
	    printf("%lld\n", sum);
	}
    return 0;
}

解決本題的關鍵是理解好kruskal算法的性質(連通性爲先,邊權追求其次)以及對不一樣狀況的全面分析

相關文章
相關標籤/搜索