有向图,从某一点出发到其他所有点的最小权值总和。
过程:
- 选入边集,找到除rot节点外,每一个点的所有入边中权值最小的,用in[]记录这个最小权值,用pre[]记录该最小入边的前驱;(若图中存在孤立节点的话,不存在最小树形图)
- 找有向环,用id[]记录节点所属环的编号。
- 将有向环缩点,并更新与之有直连边的权值。
- 以环的数量为下一次查找的点数建新图,继续重复上述过程直到没有环或者判定出不存在最小树形图为止。
//记录点的最小入边权,这个入边的前驱,i属于的环的编号;
int in[maxn],pre[maxn],idx[maxn],vis[maxn];
//点编号0~totn-1,边编号0~totm-1;
int ZhuLiu(int root,int totn,int totm){
int u,v,ans=0;
while(1){
for(int i=0;i<totn;++i) in[i]=inf;
for(int i=0;i<totm;++i){
Edge& e=edge[i];
if(e.u!=e.v&&e.w<in[e.v]){
in[e.v]=e.w;
pre[e.v]=e.u;
}
}
for(int i=0;i<totn;++i)
if(in[i]==inf&&i!=root) return -1;//孤立点,无最小树形图;
int tot=0;
memset(vis,-1,sizeof(vis));
memset(idx,-1,sizeof(idx));
in[root]=0;
for(int i=0;i<totn;++i){
ans+=in[i];
v=i;
//找环;
while(vis[v]!=i&&idx[v]==-1&&v!=root){
vis[v]=i;
v=pre[v];
}
//对环继续收缩;
if(v!=root&&idx[v]==-1){
for(u=pre[v];u!=v;u=pre[u]) idx[u]=tot;
idx[v]=tot++;
}
}
//找不到新的缩点;
if(!tot) break;
//建新图;
for(int i=0;i<totn;++i)
if(idx[i]==-1) idx[i]=tot++;
for(int i=0;i<totm;++i){
v=edge[i].v;
edge[i].u=idx[edge[i].u];
edge[i].v=idx[edge[i].v];
if(edge[i].u!=edge[i].v)
edge[i].w-=in[v];
}
totn=tot;
//保持根不变;
root=idx[root];
}
return ans;
}
一道例题:Ice_cream’s world II HDU - 2121
问存不存在一个点可以作为最小树形图的起始点。
那么最直观的想法肯定是枚举每个点作为起始点跑一次朱刘算法,但这么做会超时,那么我们可以建立一个虚拟节点,将所有节点与之相连,sum=边权为总边权和+1,ans=朱刘算法。
如果
a
n
s
>
=
2
∗
s
u
m
ans>=2*sum
ans>=2∗sum,说明起码需要两条边从虚拟节点出发,才能走遍所有的图,否则就得到了最小树形图。
该题还需要找到这个点是谁,并且如果有多个答案的话选择最小编号的节点,那么就在跑图的过程中记录一下哪个点的前驱节点是虚拟节点的话他就可能成为答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+7;
const int maxm=2e4+7;
const int inf=0x3f3f3f3f;
struct Edge{
int u,v,w,next;
}edge[maxm];
int head[maxn],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void add(int u,int v,int w){
edge[top].u=u;
edge[top].v=v;
edge[top].w=w;
edge[top].next=head[u];
head[u]=top++;
}
//记录点的最小入边,这个入边的前驱,i属于的环的编号;
int in[maxn],pre[maxn],idx[maxn],rt,vis[maxn];
//点编号0~totn-1,边编号0~totm-1;
int ZhuLiu(int root,int totn,int totm){
int u,v,ans=0;
while(1){
for(int i=0;i<totn;++i) in[i]=inf;
for(int i=0;i<totm;++i){
Edge& e=edge[i];
if(e.u!=e.v&&e.w<in[e.v]){
in[e.v]=e.w;
pre[e.v]=e.u;
if(e.u==root) rt=i;
}
}
for(int i=0;i<totn;++i)
if(in[i]==inf&&i!=root) return -1;//孤立点,无最小树形图;
int tot=0;
memset(vis,-1,sizeof(vis));
memset(idx,-1,sizeof(idx));
in[root]=0;
for(int i=0;i<totn;++i){
ans+=in[i];
v=i;
//找环;
while(vis[v]!=i&&idx[v]==-1&&v!=root){
vis[v]=i;
v=pre[v];
}
//对环继续收缩;
if(v!=root&&idx[v]==-1){
for(u=pre[v];u!=v;u=pre[u]) idx[u]=tot;
idx[v]=tot++;
}
}
//找不到新的缩点;
if(!tot) break;
//建新图;
for(int i=0;i<totn;++i)
if(idx[i]==-1) idx[i]=tot++;
for(int i=0;i<totm;++i){
v=edge[i].v;
edge[i].u=idx[edge[i].u];
edge[i].v=idx[edge[i].v];
if(edge[i].u!=edge[i].v)
edge[i].w-=in[v];
}
totn=tot;
//保持根不变;
root=idx[root];
}
return ans;
}
int main(){
int n,m,u,v,w,sum;
while(scanf("%d%d",&n,&m)!=EOF){
sum=0;
init();
for(int i=0;i<m;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
sum+=w;
}
++sum;
for(int i=0;i<n;++i) add(n,i,sum);
int ans=ZhuLiu(n,n+1,top);
if(ans==-1||ans>=2*sum) printf("impossible\n");
else printf("%d %d\n",ans-sum,rt-m);
需要注意这里不能写成edge[rt].v,因为在跑图的过程中这个点所在的环可能缩点了,
那么这个点就会改变,所以只能是最开始他代表的意义。
printf("\n");
}
return 0;
}