第21期:图论模型与算法——最小生成树(MST)

1. Kruskal算法

const int maxn=1e6;
int n,m;//点数,边数 
int u[maxn],v[maxn],w[maxn];//第i条边的两个端点序号和权值
int r[maxn];//排序后第i小的边的序号 
int cmp(const int i,const int j){ return w[i]<w[j]; }//间接比较函数
int find(int x){//并查集的find 
	return p[x]==x?x:p[x]=find(p[x]);
} 
int kruskal(){
	int ans=0;
	for(int i=0;i<n;i++) p[i]=i;//初始化并查集
	for(int i=0;i<m;i++) r[i]=i;//初始化边序号
	sort(r,r+m,cmp);//给边排序
	for(int i=0;i<m;i++){
		int e=r[i];
		int x=find[u[e]];
		int y=find[v[e]];//找出当前边两个端点所在集合编号
		if(x!=y){
			ans+=w[e];
			p[x]=y;//如果在不同集合,合并 
		} 
	} 
	return ans; 
}

2.一些题

2.1 P3366 【模板】最小生成树

#include<bits/stdc++.h>
using namespace std; 
const int maxn=1e6;
int n,m;//点数,边数 
int u[maxn],v[maxn],w[maxn];//第i条边的两个端点序号和权值
int r[maxn];//排序后第i小的边的序号 
int p[maxn];//i的根结点 
int ans,num;
int cmp(const int i,const int j){ return w[i]<w[j]; }//间接比较函数
int find(int x){//并查集的find 
	return p[x]==x?x:p[x]=find(p[x]);
} 
void kruskal(){
	ans=0;
	for(int i=0;i<n;i++) p[i]=i;//初始化并查集
	for(int i=0;i<m;i++) r[i]=i;//初始化边序号
	sort(r,r+m,cmp);//给边排序
	for(int i=0;i<m;i++){
		int e=r[i];
		int x=find(u[e]);
		int y=find(v[e]);//找出当前边两个端点所在集合编号
		if(x!=y){
			ans+=w[e];
			p[x]=y;//如果在不同集合,合并 
			num--;
		} 
	}  
}
void read_input(){
	cin>>n>>m;
	for(int i=0;i<m;i++){
		cin>>u[i]>>v[i]>>w[i];
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(NULL); cout.tie=(NULL);
	read_input();
	num=n;
	kruskal();
	if(num!=1) cout<<"orz\n";
	else cout<<ans<<"\n";
	return 0;
}

2.2 UVA1395 苗条的生成树 Slim Span

这道题类似最小生成树,只不过是让最大边权减最小边权最小。采用Kruskal求解最小生成树,只不过我们只是对于每一个边都从它开始Kruskal一遍,来算全所有情况。每一遍都更新一下答案。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+100;
const int inf=0x7fffffff;
struct edge{
	int a,b,w;
}e[maxn];
int n,m,a[maxn],b[maxn],w[maxn],fa[maxn],ans,maxed;
int find(int x){
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
bool cmp(edge a,edge b){
	return a.w<b.w;
}
bool merg(int x,int y){
	x=find(x); y=find(y);
	if(x==y) return false;
	else fa[y]=x;
	return true;
}
void init(){
	ans=inf;
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+m+1,cmp);
}
bool kruskal(int ed){
	int now=0;
	for(int i=1;i<=n;i++) fa[i]=i;
	maxed=-1;
	for(int i=ed;i<=m;i++){
		if(merg(e[i].a,e[i].b)){
			maxed=e[i].w;
			if(++now==n-1) return true;
		}
	}
	return false;
}
int main(){
	while(~scanf("%d%d",&n,&m)&&n){
		for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);
		init();
		for(int i=1;i<=m;i++){
			if(kruskal(i)){
				ans=min(ans,maxed-e[i].w);
			}
		}
		printf("%d\n",ans==inf?-1:ans);
	}
	return 0;
}

2.3 UVA1151 买还是建 Buy or Build(待补)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<map>
#include<queue>
#include<vector>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
#define ll long long

const int mx = 1111;

struct edge{
	int u, v, w;
}a[mx * mx], nw[mx*3];
int edge_size; // a为原边, nw为筛出来的边。原边个数需要记录,新边当然是n-1条。 



int T, n, m;
string line; 
int x[mx], y[mx];//题目条件 

int st[11][mx], size[11], mon[11];//套餐 

int fat[mx];//并查集 
inline int find(int x) { return fat[x] == x ? x : fat[x] = find(fat[x]); }
inline void reset() { for(int i = 1; i <= n; i++)fat[i] = i; }

//建图 
inline int diss(int i, int j) { return  (x[i]-x[j]) * (x[i]-x[j]) + (y[i]-y[j]) * (y[i]-y[j]); }
inline void addedge(int k){
	for(int i = 1; i < k; i++) {
		edge_size++;
		a[edge_size].u = i; a[edge_size].v = k; a[edge_size].w = diss(i, k);
	}
}

inline int cmp(edge x, edge y){
	return x.w < y.w;
}

//返回花费,tot为已经有了多少边,cnt为要处理的边集合的数量,op为处理旧图还是新图,cost为已经花了多少钱。 
inline int Kruskal(int tot, int cnt, int op, int cost){
	
	int sum = cost;
	if(op){
		for(int i = 1; i <= cnt; i++){
		int fu = find(a[i].u), fv = find(a[i].v);
		if(fu != fv){
			fat[fu] = fv;
			tot++;
			sum += a[i].w;
			if(op)nw[tot] = a[i];
			if(tot == n-1)break;
		}
	}
	}
	else{
		for(int i = 1; i <= cnt; i++){
		int fu = find(nw[i].u), fv = find(nw[i].v);
		if(fu != fv){
			fat[fu] = fv;
			tot++;
			sum += nw[i].w;
			if(tot == n-1)break;
		}
	}
	}
	return sum;
	
}

inline int solve(int cost){
	
	int ans = cost;
    
	for(int S = 0; S < (1<<m); S++){
		
		reset();
		
		int tot = 0;
		int need = 0;
		for(int q = 1; q <= m; q++){
			if(S & (1<<(q-1))){
			
			need += mon[q];
			for(int i = 1; i < size[q]; i++){
				for(int j = i+1; j <= size[q]; j++){
					
					int u = find(st[q][i]), v = find(st[q][j]);
					if(u != v){
						fat[u] = v;
						tot++;
					}
				}
			}
		}
		}
		ans = min(ans, Kruskal(tot, n-1, 0, need));
		
		
	}
	return ans;
}

int main(){
    cin >> T;
    while(T--){
    	edge_size = 0;
   // 	memset(size, 0, sizeof(size));
    //	memset(fat, 0, sizeof(fat));
    	
    	
    	getline(cin, line);//有没有都行
    	
    	scanf("%d %d", &n, &m);
    	
    	for(int i = 1; i <= m; i++){
    		scanf("%d %d", &size[i], &mon[i]);
    		for(int j = 1; j <= size[i]; j++)scanf("%d", &st[i][j]);
		}
    	for(int i = 1; i <= n; i++){
    		scanf("%d%d", &x[i], &y[i]);
    		addedge(i);
		}
		
		sort(a + 1, a + edge_size + 1, cmp);
		
		reset();
		
		int  cost = Kruskal(0, edge_size, 1, 0);
		
		printf("%d\n", solve(cost));
		if(T)putchar('\n');
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最小生成树(Minimum Spanning Tree, MST)是一种在图论中常见的问题,它试图在连接所有顶点的最小代价中找出一种树形结构。Prim算法是一种用于求解最小生成树算法。 Prim算法的基本思想是从图中的一个顶点开始,逐步构建最小生成树算法的主要步骤如下: 1. **初始化**:选择图中的一个顶点作为起始顶点,将该顶点加入已选顶点集合。创建一个优先队列(通常使用最小堆),并将起始顶点放入队列中。 2. **选择新顶点**:从优先队列中取出代价最小的顶点,将其加入已选顶点集合,并将其邻居顶点与当前已选顶点集合中的顶点连接起来。更新优先队列的顺序,使得新加入的顶点被优先考虑。 3. **重复**:重复步骤2,直到所有顶点都被加入已选顶点集合,或者优先队列为空。 以下是Prim算法的伪代码: ```python 初始化:选择一个顶点v作为起始顶点,创建优先队列Q while Q不为空: 从Q中取出代价最小的顶点u 将u加入已选顶点集合S 对于u的所有邻居v: 如果v不在S中: 将v加入S 更新Q中的顺序,使得v被优先考虑 return S中的所有边构成的树 ``` Prim算法的时间复杂度为O(ElogE),其中E是边的数量。这是因为每次从优先队列中取出代价最小的顶点需要O(logE)的时间,而每次更新优先队列的顺序也需要O(logE)的时间。在每一轮中,算法都会对所有顶点的邻居进行遍历,所以总的遍历次数是O(E)。因此,算法的时间复杂度取决于优先队列的实现和遍历操作的复杂度。 需要注意的是,Prim算法只返回最小生成树的所有边,而不是生成树的完整结构。因此,在构建生成树时,还需要使用其他方法来确定每条边的连接顺序和方向。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值