正题
很快啊。
这道题就是模板题了。
最小树形图指的是一个图
(
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());
}
本文详细介绍了如何构造最小树形图,通过找出最小边集、判断环与缩点操作,达到简化问题并求边权之和的目的。核心算法步骤清晰,时间复杂度为O(n^3+nm)。
5万+

被折叠的 条评论
为什么被折叠?



