【洛谷P4716】【模板】最小树形图

题目

题目链接:https://www.luogu.com.cn/problem/P4716
给定包含 \(n\) 个结点, \(m\) 条有向边的一个图。试求一棵以结点 \(r\) 为根的最小树形图,并输出最小树形图每条边的权值之和,如果没有以 \(r\) 为根的最小树形图,输出 \(-1\)
\(n\leq 100\)\(m\leq 10^4\)

思路

这道题求的是外向树,下文根据习惯,写的是内向树。只需要把所有边反过来就可以了。
首先如果这张图是一个 DAG,那么求出除了根节点外所有点的最小出边,这些出边形成的就一定是最小树形图。因为一定恰好选择 \(n-1\) 条边,且一定没有环。
所以如果除了根以外,存在一个点没有出边,那么就没有最小树形图。
对于一张普通的有向图,如果我们跑上述做法,得到的可能是若干环和一个内向树。对于一个点 \(x\),它最小的出边连向的是 \(y\),且 \(x\) 在一个环上。如果我们需要把出边改成到达 \(z\),那么它的代价是 \(dis(x,z)-dis(x,y)\)
于是可以把所有的环缩成一个点,并把选择的边的权值都加上,然后对于一条没有选择的边 \((x,z)\),如果 \(x\) 的出边是到 \(y\),把这条边的权值改为 \(dis(x,z)-dis(x,y)\) 即可。
因为如果有环,就会把环缩成一个点,每次的点数至少减 \(1\),然后可以转化为一个更小规模的相同问题。当不存在环的时候就找出了最小树形图。
时间复杂度 \(O(nm)\)

代码

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

const int N=10010,Inf=1e9;
int n,m,rt,tot,mind[N],fa[N],top[N],bel[N];

struct edge
{
	int u,v,dis;
}e[N];

int zhuliu()
{
	int ans=0;
	while (1)
	{
		for (int i=1;i<=n;i++)
			mind[i]=Inf,fa[i]=top[i]=bel[i]=0;
		mind[rt]=tot=0;
		for (int i=1;i<=m;i++)
		{
			int u=e[i].u,v=e[i].v,d=e[i].dis;
			if (u!=v && mind[v]>d) fa[v]=top[v]=u,mind[v]=d;
		}
		for (int i=1,j;i<=n;i++)
		{
			if (mind[i]==Inf) return -1;
			ans+=mind[i]; j=i;
			while (j!=rt && !bel[j] && top[j]!=i)
				top[j]=i,j=fa[j];
			if (j!=rt && !bel[j])
			{
				bel[j]=++tot;
				for (int k=fa[j];k!=j;k=fa[k]) bel[k]=tot;
			}
		}
		if (!tot) return ans;
		for (int i=1;i<=n;i++)
			if (!bel[i]) bel[i]=++tot;
		for (int i=1;i<=m;i++)
			e[i]=(edge){bel[e[i].u],bel[e[i].v],e[i].dis-mind[e[i].v]};
		n=tot; rt=bel[rt];
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&rt);
	for (int i=1;i<=m;i++)
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].dis);
	cout<<zhuliu();
	return 0;
}
posted @ 2021-08-18 20:10  stoorz  阅读(75)  评论(0编辑  收藏  举报