最小生成树模板 加 例题分析 (一次搞定最小生成树类型的问题)

最小生成树:对于一个无向连通图的最小生成树,选取边使得图中每个顶点连通且花费最小。

在kruskal算法中,集合A是一个森林,加入集合A中的安全边总是图中连接两个不同连通分支的最小权边。prim算法中,集合A仅形成单颗树,添加入集合A的安全边总是连接树与一个不在树中的顶点的最小权边。

kruskal在图G(v,e)上的运行时间取决于不相交集合数据结构是如何实现的,模板中采用路径优化的并查集,时间复杂度为O(1)。然后对时间复杂度有影响的就是对边的排序,其最终时间复杂度O(E lgV);

prim算法适用于边多的,反之为kruskal算法。鉴于kruskal代码的简单易操作,例题解法均为kruskal算法。

Kruskal伪代码:

(1)对所有的边从小到大排序

(2)while(n>1) do{

                取权值最小的边(u,v);

                if(u,v不连通){

                        将(u,v)加入T;

                        n--;

                }

                将边(u,v)从集合E中删除;

         }

其中判断两点是否连通可使用并查集

/*zhizhao zhuo
Kruskal 基本模板*/
#include<bits/stdc++.h> 
using namespace std;
const int maxn=1e3;
struct Edge{
	int from,to,dist;
	Edge(int f,int t,int d):from(f),to(t),dist(d){}
	bool operator <(const Edge& a){
		return dist<a.dist;
	}
};
vector<Edge>edges;
int pre[maxn];
int find(int x){
	int i=x;
	while(pre[i]!=i)i=pre[i];
	int j=x,k;
	while(j!=pre[j]){
		k=pre[j];
		pre[j]=i;
		j=k;
	}
	return i;
}
void joint(int x,int y){
	if(find(x)!=find(y))pre[find(x)]=find(y);
}
int kruskal(){
	int sum=0;
	sort(edges.begin(),edges.end());
	for(int i=0;i<edges.size();i++){
		int x=find(edges[i].from),y=find(edges[i].to);
		if(x!=y){
			sum+=edges[i].dist;
			pre[x]=y;
		}
	}
	return sum;
}
int main(){
	int v,e;
	while(~scanf("%d%d",&v,&e)){
		for(int i=0;i<=v;i++)pre[i]=i;
		edges.clear();
		for(int i=0;i<e;i++){
			int f,t,d;
			scanf("%d%d%d",&f,&t,&d);
			edges.push_back(Edge(f,t,d));
		}
		int ans=kruskal();
		printf("%d\n",ans);
	}
    return 0;
}

基础例题:HDU-3371 Connect the cities(会给出一些已经连接好的城市,提前加入并查集即可。遍历pre数组即可知道是否已经构成了生成树)

/*zhizhao zhuoHDU-3371 Connect the cities*/
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=500+10;
struct Edge{
	int from,to,dist;
	Edge(int f,int t,int d):from(f),to(t),dist(d){}
	bool operator <(const Edge& a){
		return dist<a.dist;
	}
};
vector<Edge>edges;
int pre[maxn],T[maxn];
int find(int x){
	int i=x;
	while(pre[i]!=i)i=pre[i];
	int j=x,k;
	while(j!=pre[j]){
		k=pre[j];
		pre[j]=i;
		j=k;
	}
	return i;
}
void joint(int x,int y){if(find(x)!=find(y))pre[find(x)]=find(y);}
int kruskal(){
	int sum=0;
	sort(edges.begin(),edges.end());
	for(int i=0;i<edges.size();i++){
		int x=find(edges[i].from),y=find(edges[i].to);
		if(x!=y){
			sum+=edges[i].dist;
			pre[x]=y;
		}
	}
	return sum;
}
int main(){
	int n,m,k,Case;
	scanf("%d",&Case);
	while(Case--){
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=n;i++)pre[i]=i;
		edges.clear();
		for(int i=0;i<m;i++){
			int f,t,d;
			scanf("%d%d%d",&f,&t,&d);
			edges.push_back(Edge(f,t,d));
		}
		for(int i=0;i<k;i++){
			int t;
			scanf("%d",&t);
			for(int j=0;j<t;j++)scanf("%d",&T[j]);
			for(int j=1;j<t;j++)joint(T[0],T[j]);
		}
		int ans=kruskal();
		bool mark=true;
		for(int i=2;i<=n;i++)if(find(1)!=find(i)){mark=false;break;}
		if(mark==true)printf("%d\n",ans);
		else printf("-1\n");
	}
    return 0;
}

理解不相交集合数据结构(并查集)对最小生成树的作用:UVA 1395 Slim Span

(构建一个生成树,使得生成树的最长边减去最短边所得的值最小。对于n个顶点的图,构成生成树至少需要n-1条边。

每次枚举一个连续的边集区间(L,R),比较每次的结果求最小值。注意提前排好序并且每次都需要将pre【】数组初始化)

/*zhizhao zhuo UVA 1395 Slim Span*/
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define LL long long
const int maxn=1e2+10,INF=1e8;
struct Edge{
	int from,to,dist;
	Edge(int f,int t,int d):from(f),to(t),dist(d){}
	bool operator <(const Edge& a){
		return dist<a.dist;
	}
};
vector<Edge>edges;
int pre[maxn];
int find(int x){
	int i=x;
	while(pre[i]!=i)i=pre[i];
	int j=x,k;
	while(j!=pre[j]){
		k=pre[j];
		pre[j]=i;
		j=k;
	}
	return i;
}
void joint(int x,int y){
	if(find(x)!=find(y))pre[find(x)]=find(y);
}
int Kruskal(int k,int n){
	int sum=0;
	int Min=INF,Max=-INF;
	for(int i=k;i<edges.size();i++){
		int x=find(edges[i].from),y=find(edges[i].to);
		if(x!=y){
			pre[x]=y;
			sum++;
			Min=min(Min,edges[i].dist);
			Max=max(Max,edges[i].dist);
		}
		if(sum==n-1)break;
	}
	if(sum==n-1)return Max-Min;
	return INF;
}
int main(){
	int n,m;
	while(scanf("%d%d",&n,&m)==2 &&n){
		int ans=INF;
		edges.clear();
		for(int i=0;i<m;i++){
			int f,t,d;
			scanf("%d%d%d",&f,&t,&d);
			edges.push_back(Edge(f,t,d));
		}
		sort(edges.begin(),edges.end());
		for(int i=0;i<edges.size();i++){
			for(int j=0;j<=n;j++)pre[j]=j;
			if(edges.size()-i>=n-1)ans=min(ans,Kruskal(i,n)); //至少要n-1条边才能构成生成树 
		}
		if(ans==INF)printf("-1\n");
		else printf("%d\n",ans);
	}
    return 0;
}

HDU-4313 Matrix(给定一些点和网络,删除网络上的边使得这些点都不连通,且花费最小。思路:不在构建最小生成树,而是构建最大生成树(边按从大到小排序),当边的两点都是给定的点时,花费加上这条边,反之加入最大生成树。最后得到的是多个包含一个给定点的最大生成树,最大生成树之间不相连,自己思考一下为什么达到了题目的条件)

/*zhizhao zhuo HDU-4313 Matrix*/
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define LL long long
const int maxn=1e5+10;
struct Edge{
	int from,to,dist;
	Edge(int f,int t,int d):from(f),to(t),dist(d){}
	bool operator <(const Edge& a){
		return dist>a.dist;
	}
};
vector<Edge>edges;
int pre[maxn],v[maxn];
int find(int x){
	int i=x;
	while(pre[i]!=i)i=pre[i];
	int j=x,k;
	while(j!=pre[j]){
		k=pre[j];
		pre[j]=i;
		j=k;
	}
	return i;
}
/*
void joint(int x,int y){
	if(find(x)!=find(y))pre[find(x)]=find(y);
}*/
LL kruskal(int k){
	LL sum=0;
	sort(edges.begin(),edges.end());
	for(int i=0;i<edges.size();i++){
		int x=find(edges[i].from),y=find(edges[i].to);
		if(v[x]&&v[y]){
			sum+=edges[i].dist;continue;
		}
		else if(v[x])pre[y]=x;
		else pre[x]=y;
	}
	return sum;
}
int main(){
	int T,N,K;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&N,&K);
		memset(v,0,sizeof(v));
		for(int i=0;i<=N;i++)pre[i]=i;
		edges.clear();
		for(int i=0;i<N-1;i++){
			int f,t,d;
			scanf("%d%d%d",&f,&t,&d);
			edges.push_back(Edge(f,t,d));
		}
		for(int i=0;i<K;i++){
			int t;
			scanf("%d",&t);
			v[t]=1;
		}
		LL ans=kruskal(K);
		printf("%lld\n",ans);
	}
    return 0;
}

理解生成树由n-1条边组成,学会子集生成:UVA 1151 Buy or Build(给一张图和几个子网,子网已经连通且花费固定,可以直接连通两点或者采用子网,使得把图连通的花费最小)

运用子集生成枚举子网,将子网中的点加入并查集,再构建生成树

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1000 +10;
const int maxq=9;
int xx[maxn],yy[maxn],cost[maxq];
int pre[maxn];                         //并查集数组
vector<int >subn[maxq];                //子网载体
struct Edge{
    int u,v,d;
    Edge(int u,int v,int d):u(u),v(v),d(d){}  //方便后序的插入操作
    bool operator <(const Edge &a)            //用于排序
    const  {return d<a.d;}
};
int find(int lin){                     //并查集查找函数(路径优化)
    int i=lin;
    while(pre[i]!=i)i=pre[i];
    int j=lin,k;
    while(j!=pre[j]){
        k=pre[j];
        pre[j]=i;
        j=k;
    }
    return i;
}
int MST(int cnt,const vector<Edge> & e,vector<Edge>& used){ //最小生成树 重点为cnt的值
    if(cnt==1)return 0;
    int ans=0;
    int m=e.size();
    used.clear();
    for(int i=0;i<m;i++){
        int x=find(e[i].u),y=find(e[i].v);
        int d=e[i].d;
        if(x!=y){
            pre[x]=y;
            ans+=d;
            used.push_back(e[i]);    //装入生成MST所需要用到的边,用于后续的子集生成模块
            if(--cnt==1)break;
        }
    }
    return ans;
}

int main(){
    int T,n,q;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&q);
        for(int i=0;i<q;i++){
            int sum,c,temp;
            scanf("%d%d",&sum,&c);
            cost[i]=c;
            subn[i].clear();
            while(sum--){
                scanf("%d",&temp);
                subn[i].push_back(temp-1);
            }
        }
        for(int i=0;i<n;i++)scanf("%d%d",&xx[i],&yy[i]);
        
        vector<Edge> e, need;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                int c=(xx[i]-xx[j])*(xx[i]-xx[j])+(yy[i]-yy[j])*(yy[i]-yy[j]);
                e.push_back(Edge(i,j,c));
            }
        }
        
        for(int i=0;i<n;i++)pre[i]=i;
        sort(e.begin(),e.end());
        int ans=MST(n,e,need);
        
        vector<Edge>sun;
        for(int mask=0;mask< (1<<q);mask++){     //子集生成模块
            int cnt=n,c=0;
            for(int i=0;i<n;i++)pre[i]=i;        //子网中加入的点与后序的MST都是这一个集合,子网已连接的在后续MST中不会再查找
            for(int i=0;i<q;i++)if(mask& (1<<i)){
                c+=cost[i];
                int ss=subn[i].size();
                for(int j=0;j<ss;j++){
                    int u=find(subn[i][j]),v=find(subn[i][0]);   //将子网中的点加入集合。
                    if(u!=v){pre[u]=v;--cnt;}
                }
            }
            ans=min(ans, c+ MST(cnt,need,sun)); // 二次MST
        }
        printf("%d\n",ans);
        if(T)printf("\n");
    }
    return 0;
}

持续更新中。。。。。。

猜你喜欢

转载自blog.csdn.net/love_phoebe/article/details/81367157