zoukankan      html  css  js  c++  java
  • 最小树形图——朱刘算法学习小记

    参考资料: https://www.cnblogs.com/hdu-zsk/p/8167687.html https://www.luogu.com.cn/blog/xiaojiji/solution-p4716


    一张无向图,你要用边权和最小的边来使所有点联通,显然用最小生成图。 现在的问题是,给你一张有向图并且钦定一个起点$r$,你要选边权和最小的边使$r$可以到达任意点。 这就是最小树形图问题。


    上面的第一篇博客有演示过程,讲得比较详细。所以这里就随便胡一下:

    1. 对于$r$之外的每个点$x$,找到连向$x$的边权最小的边(自环除外),记为$mn_x$。
    2. 如果存在点$x$满足没有这样的边连向它,那么它被孤立了,不可能有解。
    3. 将这些边都选出来,如果没有环,意味着最小树形图已经被找出来了;否则进入下一步的缩环操作。
    4. 对于每个在环中的点$x$,连向$x$的边中,如果起点不在这个环内,则用其边权减去连向$mn_x$的边权。然后将整个环缩成一个点。
    5. 对于每个点$x$,将$mn_x$的权值加入答案。

    一直循环操作直到退出。 如果要得出具体的边是什么,稍微处理一下$mn_x$应该就可以了(设连向$x$的边记为$ef_x$。在处理$mn_x$的时候,由于$x$可能是被缩过的,找到$mn_x$连向的具体的点$y$,然后覆盖$ef_y$。)

    这还是挺容易感性理解的。 边权减去$mn_x$的边权,因为$mn_x$的权值已经加入答案了。如果后面要选择这条边,就应该要替换$mn_x$。所以要用其边权减去$mn_x$的边权。

    这样时间复杂度是$O(nm)$,因为每轮至少会少一个连通块。


    模板

    洛谷和LOJ上都有板题。

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 110
    #define M 10010
    #define INF 1000000000
    int n,m,r;
    struct edge{int u,v,w;} ed[M];
    int pre[N],mn[N];
    int id[N],cnt;
    int vis[N];
    int zhu_liu(){
    	int res=0;
    	while (1){
    		for (int i=1;i<=n;++i)
    			mn[i]=INF;
    		for (int i=1;i<=m;++i){
    			int u=ed[i].u,v=ed[i].v,w=ed[i].w;
    			if (u!=v && w<mn[v])
    				mn[v]=w,pre[v]=u;
    		}
    		for (int i=1;i<=n;++i)
    			if (i!=r && mn[i]==INF)
    				return -1;
    		memset(vis,0,sizeof(int)*(n+1));
    		memset(id,0,sizeof(int)*(n+1));
    		cnt=0;
    		for (int i=1;i<=n;++i){
    			if (i==r)
    				continue;
    			res+=mn[i];
    			int x=i;
    			for (;vis[x]!=i && !id[x] && x!=r;x=pre[x])
    				vis[x]=i;
    			if (x!=r && !id[x]){
    				id[x]=++cnt;
    				for (int y=pre[x];y!=x;y=pre[y])
    					id[y]=cnt;
    			}
    		}
    		if (cnt==0)
    			break;
    		for (int i=1;i<=n;++i)
    			if (!id[i])
    				id[i]=++cnt;
    		for (int i=1;i<=m;++i){
    			int u=ed[i].u,v=ed[i].v,w=ed[i].w;
    			ed[i]={id[u],id[v],w-(id[u]!=id[v]?mn[v]:0)};
    		}
    		n=cnt;
    		r=id[r];
    	}
    	return res;
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&r);
    	for (int i=1;i<=m;++i){
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		ed[i]=(edge){u,v,w};
    	}
    	printf("%d
    ",zhu_liu());
    	return 0;
    }
    

    Acceleration

    上面第二篇博客有介绍,这里复述一下: 换一下写朱刘算法的姿势: 枚举点$x$,找与$x$相连的最小边$mn_x$,将其加进来。如果这个时候出现了环,就像上面那样缩环并且修改外面连向环中的点的边的边权,重复操作。 考虑优化这个过程,给每个点开个左偏树,左偏树中存所有连向这个点的边。左偏树支持合并,并且这里还要支持对整个左偏树进行整体减。缩环的时候将环上所有点的左偏树进行整体减,然后合并到一起。 另外用并查集来维护每个具体的点被缩到了哪个点,还要用并查集来查是否出现环。详见代码。 时间复杂度变成了$O((n+m)lg m)$

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    #define N 110
    #define M 1010
    #define INF 1000000000
    int n,m,r;
    struct Node* null;
    struct Node{
    	Node *l,*r;
    	int d;
    	int u,w,tag;
    	void gt(int c){w+=c,tag+=c;}
    	void pd(){l->gt(tag),r->gt(tag),tag=0;};
    };
    Node *newnode(int u,int w){
    	Node *nw=new Node;
    	*nw={null,null,1,u,w,0};
    	return nw;
    }
    Node *merge(Node *a,Node *b){
    	if (a==null) return b;
    	if (b==null) return a;
    	if (a->w>b->w) swap(a,b);
    	a->pd();
    	a->r=merge(a->r,b);
    	if (a->l->d<a->r->d)
    		swap(a->l,a->r);
    	a->d=a->r->d+1;
    	return a;
    }
    void pop(Node *&t){
    	if (t==null) return;
    	Node *tmp=merge(t->l,t->r);
    	delete t;
    	t=tmp;
    }
    Node *in[N];
    int fa[N],bel[N];
    int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
    int getbel(int x){return bel[x]==x?x:bel[x]=getbel(bel[x]);}
    int pre[N],mn[N];
    int ZLA(){
    	int res=0;
    	for (int i=1;i<=n;++i)
    		bel[i]=i,fa[i]=i;
    	for (int i=1;i<=n;++i)
    		if (i!=r){
    			int x=getbel(i);
    			while (1){
    				while (in[x]!=null && getbel(in[x]->u)==x)
    					pop(in[x]);
    				if (in[x]==null)
    					return -1;
    				pre[x]=getbel(in[x]->u);
    				res+=mn[x]=in[x]->w
    				int y=pre[x];
    				if (x!=getfa(y)){
    					fa[x]=getfa(y);
    					break;
    				}
    				in[x]->gt(-mn[x]);
    				for (;y!=x;y=getbel(pre[y])){
    					bel[y]=x;
    					in[y]->gt(-mn[y]);
    					in[x]=merge(in[x],in[y]);
    				}
    			}
    		}
    	return res;
    }
    int main(){
    //	freopen("in.txt","r",stdin);
    	scanf("%d%d%d",&n,&m,&r);
    	null=new Node;
    	*null={null,null,0,0,0,0};
    	for (int i=1;i<=n;++i)
    		in[i]=null;
    	for (int i=1;i<=m;++i){
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		in[v]=merge(in[v],newnode(u,w));
    	}
    	printf("%d
    ",ZLA());
    	return 0;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

  • 相关阅读:
    mongo连接拒绝10061原因
    python爬取CNKI的期刊
    C语言socket编程
    Linux c time模块函数库
    linux下python3调用c代码或者python3调用c++代码
    stl综合
    linux c调用 mysql代码
    debian系列下c++调用mysql, linux下面安装mysql.h文件
    c++ linux socket编程 c++网络编程
    比较均值分析思路
  • 原文地址:https://www.cnblogs.com/jz-597/p/13411769.html
Copyright © 2011-2022 走看看