• 最小树形图学习笔记


    最小树形图

    朱刘算法


    • 问题引入:

    给你nn个点mm条边的有向图,边有边权,给定一个根,你可以选择一些边,求以给定根为根的有向生成树的最小代价(有向生成树就是边的方向为父亲指向儿子)。

    • 分析:

    当然,有向的话我们不能用最小生成树算法了。
    我们要先提前判断一下图的连通性(如果不连通也就不存在最小树形图了)。

    现在,我们要找出一个使得一个有向图连通的最小权值的(一般为边权和,有时会有点权,而且有可能点权会随着边的选择变化)有根树,那么可以贪心的考虑,我们对于nn个点的图,我们选出每个点入边中权值最小的一条边然后看能不能构成一棵树。

    因为nn个点会选出n1n-1条边(当然我们不选根的入边),如果这nn个点n1n-1条边组成的新图没有环,那么当前肯定就是答案了。否则有环的话当前的图肯定不连通,所以我们通过缩环,构成新的图然后重复上面的操作来找,直到没有环。

    但是一个环,你肯定是要用一条边换掉环上的一条边,使其成为树。我们可以在缩完环后,直接枚举边,然后将权值更新为减去当前的去的点的最小入边。

    重复这样的操作,最后得到的就是答案啦。

    复杂度最坏为O(nm)O(nm)nn为点数mm为边数),但是tarjan神仙有O(m+nlogn)O(m+nlogn)的做法,蒟蒻没找到,希望有大神教教蒟蒻,QAQ

    (先简略的说明一下,后面再填坑填详细一点)

    下面给出洛谷模板题代码,仅供参考【题目IN洛谷

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    const int M=1e5+10;
    const int inf=0x7fffffff;
    int n,m,root;
    int low[M],f[M],bel[M],vis[M];
    int fa[M];
    int find(int a){return fa[a]==a?a:fa[a]=find(fa[a]);}
    void merge(int a,int b){
    	a=find(a);b=find(b);
    	if(a!=b)fa[a]=b;
    }
    bool check(){
    	int a=find(1);
    	for(int i=2;i<=n;i++){
    		if(find(i)!=a) return 0;
    	}
    	return 1;
    }
    struct ss{
    	int u,v,w;
    	ss(){}
    	ss(int a,int b,int c):u(a),v(b),w(c){}
    }e[M];
    int cnt;
    int min_g(){
    	int k,ans=0;
    	if(!check()) return -1;//判断连通
    	while(1){
    		for(int i=1;i<=n;i++)low[i]=inf,vis[i]=f[i]=bel[i]=0;
    		for(int i=1;i<=m;i++){
    			if(e[i].u!=e[i].v&&e[i].w<low[e[i].u]){
    				f[e[i].u]=e[i].v;
    				low[e[i].u]=e[i].w;
    			}//找最小入边
    		}
    		low[root]=cnt=0;
    		for(int i=1;i<=n;i++)if(low[i]==inf) return -1;
    		for(int i=1;i<=n;i++){
    			ans+=low[i];
    			//加入答案并找出环
    			for(k=i;((!bel[k])&&(vis[k]!=i)&&(k!=root));k=f[k])vis[k]=i;
    			if((k!=root)&&!bel[k]){
    				bel[k]=++cnt;
    				for(int j=f[k];j!=k;j=f[j])bel[j]=cnt;
    			}
    		}
    		if(!cnt) return ans;//没有环了就是答案
    		for(int i=1;i<=n;i++)if(!bel[i])bel[i]=++cnt;//缩环,重新编号
    		for(int i=1;i<=m;i++){
    			if(e[i].u!=e[i].v)e[i].w-=low[e[i].u];//更新为差值,因为每次都是加上了的,所以换一条边相当于再加上差值
    			e[i].v=bel[e[i].v];e[i].u=bel[e[i].u];
    		}
    		n=cnt;root=bel[root];//重新给点数和根赋值
    	}
    	return ans;
    }
    int a,b,c,tc;
    int main(){
    	scanf("%d%d%d",&n,&m,&root);
    	for(int i=1;i<=n;i++)fa[i]=i;
    	for(int i=1;i<=m;i++){
    		++tc;
    		scanf("%d%d%d",&e[tc].v,&e[tc].u,&e[tc].w);
    		if(e[tc].u==root)--tc;//有根的边不要。
    		else merge(e[tc].u,e[tc].v);
    	}
    	m=tc;
    	printf("%d
    ",min_g());
    	return 0;
    }
    

    另外一道类似的模板题
    BZOJ4349【IN
    双倍经验【IN

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define db double
    #define inf 1e20
    using namespace std;
    const int M=2e4+10;
    int f[M],bel[M],vis[M];
    int n,m,nm,tot,id[M],times[M],root;
    db lowest[M],cost[M],ans;
    struct ss{
        int u,v;db w;
        ss(){}
        ss(int a,int b,db c):u(a),v(b),w(c){}
    }g[M];
    void min_gt(){
        int k,cnt=0;
        while(1){
            for(int i=1;i<=n;i++)lowest[i]=inf,f[i]=bel[i]=vis[i]=0;
            for(int i=1;i<=m;i++){
                if(g[i].v!=g[i].u&&g[i].w<lowest[g[i].u])
                    f[g[i].u]=g[i].v,lowest[g[i].u]=g[i].w;
            }
            lowest[root]=0;cnt=0;
            for(int i=1;i<n;i++){
                ans+=lowest[i];
                for(k=i;((!bel[k])&&(vis[k]!=i)&&(k!=root));k=f[k])vis[k]=i;
                if((k!=root)&&(!bel[k])){
                    bel[k]=++cnt;
                    for(int j=f[k];j!=k;j=f[j])bel[j]=cnt;
                }
            }
            if(!cnt)return;
            for(int i=1;i<=n;i++)if(!bel[i])bel[i]=++cnt;
            for(int i=1;i<=m;i++){
                db now=lowest[g[i].u];
                g[i].v=bel[g[i].v];g[i].u=bel[g[i].u];
                if(g[i].u!=g[i].v)g[i].w-=now;
            }
            n=cnt;root=bel[root];
        }
    }
    db a;int b,c;
    int main(){
        scanf("%d",&n);
        //0~n-1,新建超级根n
        for(int i=1;i<=n;++i){
            scanf("%lf%d",&a,&b);
            if(b){
                id[i]=++tot;lowest[tot]=inf;
                cost[tot]=a;times[tot]=b;
                g[tot].u=tot;g[tot].w=a;
            }
        }
        n=tot+1;root=n;m=tot;
        for(int i=1;i<n;i++)g[i].v=n,lowest[i]=cost[i];
        scanf("%d",&nm);
        for(int i=1;i<=nm;i++){
            scanf("%d%d%lf",&b,&c,&a);
            if(!id[b]||!id[c]||b==c) continue;
            g[++m].v=id[b];g[m].u=id[c];g[m].w=a;
            lowest[g[m].u]=min(lowest[g[m].u],a);
        }
        for(int i=1;i<n;i++)if(times[i]>1)ans+=(times[i]-1)*lowest[i];
        min_gt();
        printf("%.2lf
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    Java NIO类库Selector机制解析(上)
    SWT Display.getDefault() 和Display.getCurrent()的区别
    VSS 2005 复位 工作目录(Reset Working Folder)
    转:理解javascript中的delete机制(2)
    在 .NET Framework 2.0 中未处理的异常导致基于 ASP.NET 的应用程序意外退出
    CSS样式
    Character Animator不显示NDI无法OBS直播
    【LoadRunner】基础使用教程:录制第一个脚本(包含遇到的错误问题解决)
    流式传输 之四流式协议
    全局变量,静态变量,局部变量
  • 原文地址:https://www.cnblogs.com/VictoryCzt/p/10053415.html
Copyright © 2020-2023  润新知