【洛谷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;
}