最小树形图(朱刘算法)模板

本文深入探讨了最小树形图问题,详细介绍了朱刘算法的实现原理与过程,包括贪心策略、环的处理及tarjan优化技巧,通过代码示例展示了算法的具体应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最小树形图:有向图,求以某个确定的点为根的外向最小生成树。

最小树形图朱刘算法:https://www.luogu.com.cn/blog/i207M/shu-xing-tu-shu-liu-suan-fa-xue-xi-bi-ji-xie-ti-bao-gao-p4716-post

tarjan优化:https://www.luogu.com.cn/blog/xiaojiji/solution-p4716

简单地阐述一下想法:

贪心,合法解每个点(除了根)肯定有一条入边,于是每个点选择一条最小的入边,形成一个子图,如果有点没有入边无解。

这个子图的权值肯定小于等于最优解,然而并不一定是棵树,我们找出其中的环。

将环缩为点,所有连向这个环的边的权值改为原值减去对应点在环中入边的权值,并将答案加上环上边的权值。

此时一条指向环的入边代表替换了环中的一条边。

新图的最小树形图加上已有答案就是原图的最小树形图,考虑从原图的最小树形图构造出新图的最小树形图来证明:(截图自链接中的第二篇博客)

每一轮至少会减少一个点,每轮复杂度O(m),总复杂度O(nm)。

#include<cstdio>
#include<cstring>
#define maxn 105
#define maxm 10005
int n,m,r,ans,INF=1e9;
int x[maxm],y[maxm],cst[maxm];
int in[maxn],vis[maxn],scc[maxn],pre[maxn];
int zhuliu()
{
	int ans=0,v,cnt=0;
	while(1)
	{
		for(int i=1;i<=n;i++) in[i]=INF,scc[i]=vis[i]=0;
		for(int i=1;i<=m;i++)
			if(x[i]!=y[i]&&cst[i]<in[y[i]])
				pre[y[i]]=x[i],in[y[i]]=cst[i];
		in[r]=0;
		for(int i=1;i<=n;i++) if(in[i]==INF) return -1;
		for(int i=1;i<=n;i++)
		{
			ans+=in[i];//这里没有成环的也把边加入答案中,方便下面的修改,显然先加入一条边然后把周围的边减去这个权值后继续选边是等价的
			for(v=i;!vis[v]&&v^r;v=pre[v]) vis[v]=i;
			if(v!=r&&vis[v]==i)
			{
				scc[v]=++cnt;
				for(v=pre[v];!scc[v];v=pre[v]) scc[v]=cnt;
			}
		}
		if(!cnt) break;
		for(int i=1;i<=n;i++) if(!scc[i]) scc[i]=++cnt;
		for(int i=1;i<=m;i++)
		{
			v=y[i];
			x[i]=scc[x[i]],y[i]=scc[y[i]];
			if(x[i]!=y[i]) cst[i]-=in[v];
		}
		n=cnt;cnt=0;
		r=scc[r];
	}
	return ans;
}
int main()
{
	scanf("%d%d%d",&n,&m,&r);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&x[i],&y[i],&cst[i]);
	printf("%d\n",zhuliu());
}

LOJ#6510. 「雅礼集训 2018 Day8」A:求无根最小树形图

加一个虚根0连向所有的点边权inf,如果有解最多选一条,如果无解则最终ans>=2*inf

#include<bits/stdc++.h>
#define maxn 505
#define maxm maxn*maxn
#define LL long long
using namespace std;
const LL inf = 1ll<<50;
int n,m,x[maxm],y[maxm],vis[maxn],pre[maxn],scc[maxn],N,M;
LL w[maxm],in[maxn],ans;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d%lld",&x[i],&y[i],&w[i]);
	for(int i=1;i<=n;i++) x[++m]=0,y[m]=i,w[m]=inf;
	while(1){
		for(int i=1;i<=n;i++) in[i]=inf+1,scc[i]=vis[i]=0;
		for(int i=1;i<=m;i++)
			if(x[i]^y[i]&&w[i]<in[y[i]]) in[y[i]]=w[i],pre[y[i]]=x[i];
		for(int i=1;i<=n;i++){
			ans+=in[i]; int x=i;
			for(;!vis[x]&&x;x=pre[x]) vis[x]=i;
			if(x&&vis[x]==i){
				scc[x]=++N;
				for(int y=pre[x];y^x;y=pre[y]) scc[y]=N;
			}
		}
		if(!N) break;
		for(int i=1;i<=n;i++) if(!scc[i]) scc[i]=++N;
		for(int i=1;i<=m;i++){
			int u=scc[x[i]],v=scc[y[i]];
			if(u^v) w[++M]=w[i]-in[y[i]],x[M]=u,y[M]=v;
		}
		n=N,m=M,N=M=0;
	}
	printf("%lld\n",ans>=inf+inf?-1:ans-inf);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值