最大伪森林——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算法的性质(连通性为先,边权追求其次)以及对不一样状况的全面分析

相关文章
相关标签/搜索