最小树形图(有向图的最小生成树)

最小树形图:

最小树形图:求的就是有向图的最小生成树的权值和

模版:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;
const int inf = 0x3f3f3f3f;

struct Edge{
	int x;
	int y;
	int w;
}edge[maxn];
int vis[maxn];
int id[maxn];//结点所属环的编号
int in[maxn];//in[]为最小入边权
int pre[maxn];//pre[]为其对应的起点

int zhuLiu(int root,int n,int m){//root结点、点数、边数
	int res = 0;//最小树形图的总权值
	while(1){
		for(int i = 0;i < n;i++){//初始化为无穷大
			in[i] = inf;
		}
		
		//寻找每个点的最小入边
		for(int i = 0;i < m;i++){//遍历每条边
			int x = edge[i].x;
			int y = edge[i].y;
			if(edge[i].w < in[y]&&x != y){//更新最小入边
				pre[y] = x;//记录前驱
				in[y] = edge[i].x;//更新
			}
		}

		//判断是否存在最小树形图
		for(int i = 0;i <  n;i++){
			if(i == root){
				continue;
			}
			if(in[i] == inf)//除根结点外的点存在孤立点
				return -1;
		}

		//寻找所有的环
		int cnt = 0;//记录环数
		in[root] = 0;
		memset(id,-1,sizeof(id));
		memset(vis,-1,sizeof(vis));
		for(int i  = 0;i < n;i++){//标记每个环
			res += in[i];//记录权值???

			int y = i;
			while(vis[y] != i&&id[y] != -1&&y != root){//寻找图中的有向环
				//三种情况会终止:找到出现同样标记的点、结点已属于其他环、遍历到根
				vis[y] = i;//标记
				y = pre[y];//向上找
			}

			if(y != root&&id[y] != -1){//没有遍历到根或者没有找到结点属于其他环,说明找到有向环
				for(int x = pre[y];x != y;x = pre[x]){//标记结点x为第几个有向环
					id[x] = cnt;//记录结点所属环号
				}
				id[y] = cnt++;//记录结点所属环号并累加
			}
		}
		if(cnt == 0) break;//无环
		for(int i = 0;i < n;i++){//可能存在独立点
			if(id[i] == -1) id[i] = cnt++;//环数累加
		}

		//建立新图,缩点重新标记
		for(int i = 0;i < m;i++){
			int x = edge[i].x;
			int y = edge[i].y;
			edge[i].x = id[x];
			edge[i].y = id[y];
			if(id[x] != id[y]){//两点不再统一环内,更新边权值
				edge[i].w -= in[y];//x到y的距离为边权-in[y]???
			}
		}

		n = cnt;
		root = id[root];
	}
	return res;
}


int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= m;i++){
		scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].w);
		if(edge[i].x == edge[i].y){//除去自环,即点到自身的距离为inf
			edge[i].w = inf;
		}
	}
	int res = zhuLiu(0,n,m);
	if(res == -1){
		printf("No\n");
	}
	else printf("%d\n",res);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值