最小树形图,一个只有模板题的算法

本文详细介绍了如何构造最小树形图,通过找出最小边集、判断环与缩点操作,达到简化问题并求边权之和的目的。核心算法步骤清晰,时间复杂度为O(n^3+nm)。

正题

很快啊。
这道题就是模板题了。
最小树形图指的是一个图 ( V , E ) (V,E) (V,E),满足其为弱连通图且存在一个点没有入边,其他点恰好一条入边的有向图,也就是一个有向外向树的形式。
算法流程是这样的:
1.找出最小边集。由于在这个图中,除了根节点外每一个点都有且仅有一个入度,所以我们可以从这里入手。因此找出每一个点的入边中权值最小的边mmin[i],同时如果存在一个除了根节点以外的点没有入边,则可判定为无解
2.判断是否有环。怎么判有环可以直接看代码,如果没有环而且没有缩点,那么直接返回就好了。如果有环则跳转3,否则跳转4。
3.有环则将其缩为一个点,对于该点中的连边可以直接忽视不看,对于连入该点的边(u,v,c),需将权值改为c-mmin[v],因为这是考虑到切换边的差分贡献。
4.拆点,将连入一个缩点且选中的边(u,v)保留,对于v点之前所连的最小边去除,其他边都保留。

这样我们就完成了最小树形图的整个流程,十分简明易懂。
由于大部分题目(模板题)叫我们求的只是边权之和。
所以我们可以直接缩点不展开。
时间复杂度为 O ( n 3 + n m ) O(n^3+nm) O(n3+nm),时间复杂度瓶颈在找环上。

#include<bits/stdc++.h>
using namespace std;

const int N=110,M=10010;
struct edge{
	int x,y,c;
}s[M];
int n,m,rt,mmin[N],pre[N],where[N],vis[N];

int solve(){
	int tot=0;
	while(1){
		for(int i=1;i<=n;i++)
			mmin[i]=1e9,vis[i]=0,where[i]=0;
		for(int i=1;i<=m;i++) if(s[i].x!=s[i].y && s[i].c<mmin[s[i].y])
			mmin[s[i].y]=s[i].c,pre[s[i].y]=s[i].x;
		for(int i=1;i<=n;i++) if(i!=rt && mmin[i]==1e9) return -1;
		int cnt=0;
		for(int i=1;i<=n;i++) if(i!=rt){
			int now=i;tot+=mmin[i];
			while(vis[now]!=i && !where[now] && now!=rt) vis[now]=i,now=pre[now];
			//这行表示:停止的条件要么碰到这个循环点了,要么碰到之前的环了,要么碰到根了
			if(vis[now]==i){//当且仅当碰到循环点才可能找到新的环(废话
				where[now]=++cnt;
				for(int j=pre[now];j!=now;j=pre[j]) where[j]=cnt;
			}
		}
		if(cnt==0) break;
		for(int i=1;i<=n;i++) if(!where[i]) where[i]=++cnt;
		for(int i=1;i<=m;i++) s[i].c-=mmin[s[i].y],s[i].x=where[s[i].x],s[i].y=where[s[i].y];
		n=cnt;rt=where[rt];
	}
	return tot;
}

int main(){
	scanf("%d %d %d",&n,&m,&rt);
	for(int i=1;i<=m;i++) scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].c);
	printf("%d\n",solve());
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值