11-03最小生成树

最小生成树

一、定义

      在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集且为无循环图,使得联通所有结点的的 w(T) 最小,则此 T 为 G 的最小生成树。 w(t) = $\sum_{(u,v)∈t}$w(u,v)
      最小生成树是最小权值生成树的简称

二、算法Kruskal原理

      假设给出了含有n个顶点的连通图,边有若干条,带有自己的权值。
      1.先构建一个只含有n个顶点,边集为空的子图
      2.按照边权大小进行排序,多数从小到大排序
      3.按顺序依次选取边,判断两端点是否已经同属于一个集合(并查集)一个集合则不选取该边,非一个集合选取该边
      4.直到将所有边选完,合并n-1次即可连通,且因为是排序后从小到大进行的选择,将不会有更优的选择,即可求出最小生成树

三、模板代码实现

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 5e3 + 10, M = 2e5 + 10;

int n, m, cnt, sum;
int f[N];

struct node {
	int x, y, z;
}tr[M];

bool cmp(node u, node v) {
	return u.z < v.z;
}

void init(int n) {
	for (int i = 1; i <= n; i++)
		f[i] = i;
}

int find(int x) {
	return f[x] == x ? x : f[x] = find(f[x]);
}

void merge(int x, int y) {
	x = find(x);
	y = find(y);
	if (x != y) {
		f[x] = y;
	}
}

int main() {
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		cin >> tr[i].x >> tr[i].y >> tr[i].z;
	}
	sort(tr, tr + m, cmp);
	init(n);
	cnt = n, sum = 0;
	for (int i = 0; i < m; i++) {
		int x = tr[i].x;
		int y = tr[i].y;
		if (find(x) != find(y)) {
			merge(x, y);
			cnt--;
			sum += tr[i].z;
		}
	}
	if (cnt != 1) {
		cout << "orz" << endl;
		return 0;
	}
	cout << sum << endl;
	return 0;
} 

四、相关题

1.P2330 繁忙的都市【普及/提高-】

题意:
      给出n,m,表示有n个交叉路口(顶点),m条道路道路(带权边),以下给出m行,每行u,v,c,表示交叉路口u(顶点u)和交叉路口v(顶点v)之间有道路相连,分值为c,需要使得通过选边,使得任意两个交叉路口(顶点)可以互通,同时分值尽可能少,输出选择了几条路,分值最大的那条道路的分值
思路:
      最小生成树
实现:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 5e3 + 10, M = 2e5 + 10;

int n, m, cnt, sum;
int f[N];

struct node {
	int x, y, z;
}tr[M];

bool cmp(node u, node v) {
	return u.z < v.z;
}

void init(int n) {
	for (int i = 1; i <= n; i++)
		f[i] = i;
}

int find(int x) {
	return f[x] == x ? x : f[x] = find(f[x]);
}

void merge(int x, int y) {
	x = find(x);
	y = find(y);
	if (x != y) {
		f[x] = y;
	}
}

int main() {
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		cin >> tr[i].x >> tr[i].y >> tr[i].z;
	}
	sort(tr, tr + m, cmp);
	init(n);
	cnt = n, sum = 0;
	for (int i = 0; i < m; i++) {
		int x = tr[i].x;
		int y = tr[i].y;
		if (find(x) != find(y)) {
			merge(x, y);
			cnt--;
			sum = tr[i].z;
		}
	}
	cout << n - 1 << ' ' << sum << endl;
	return 0;
} 

2.CF1245D Shichikuji and Power Grid【普及+/提高】

题意:
      点权为 c[i], i和 j 两个城市相连的代价是 k[i]+k[j] 乘上两者的曼哈顿距离求最小代价的方案,给出n,表示n个城市,以下n个城市的坐标,n个点权c[i],n个k[i],输出分三类,首先输出最小代价,换行输出有几个城市建立了发电站,换行输出哪几个城市建立了发电站,输出有几条连线,以下输出每条连线,顺序任意
思路:
      设虚拟节点,将边权转化为点权,放入vector计入选取的情况数,这里将n+1虚拟节点建立,连接n个节点与第n+1的节点,将点权转化为边权,最小生成树即可
实现:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2e3 + 10, M = 2e5 + 10;

int n, m, cnt;
long long sum;
int f[N];

struct node {
	int x, y;
	long long c, k, w;
}tr[M];

struct node1 {
	int u, v;
	long long w;
}tr1[N * N];

vector<node1> ans;

bool cmp(node1 u, node1 v) {
	return u.w < v.w;
}

void init(int n) {
	for (int i = 1; i <= n; i++)
		f[i] = i;
}

int find(int x) {
	return f[x] == x ? x : f[x] = find(f[x]);
}

void merge(int x, int y) {
	x = find(x);
	y = find(y);
	if (x != y) {
		f[x] = y;
	}
}

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> tr[i].x >> tr[i].y;
	}
	for (int i = 1; i <= n; i++) {
		cin >> tr[i].c;
	}
	for (int i = 1; i <= n; i++) {
		cin >> tr[i].k;
	}
	cnt = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = i; j <= n; j++) {
			if (i == j){
				tr1[cnt++] = {i, n + 1, tr[i].c};
			} else {
				tr1[cnt++] = {i, j, (tr[i].k + tr[j].k) * (abs(tr[i].x - tr[j].x) + abs(tr[i].y - tr[j].y))};
			}
		}
	}
	sort(tr1, tr1 + cnt, cmp);
	init(n+1);
	sum = 0;
	int sum1 = 0, sum2 = 0;
	for (int i = 0; i < cnt; i++) {
		int x = tr1[i].u;
		int y = tr1[i].v;
	}
	for (int i = 0; i < cnt; i++) {
		int x = tr1[i].u;
		int y = tr1[i].v;
		if (find(x) != find(y)) {
			merge(x, y);
			sum += tr1[i].w;
			ans.push_back(tr1[i]);
			if (y == n + 1) sum1++;
			sum2++;
		}
	}
	cout << sum << endl;
	cout << sum1 << endl;
	for (int i = 0; i < ans.size(); i++) {
		if (ans[i].v == n + 1) {
			cout << ans[i].u << ' ';
		}
	}cout << endl;
	cout << sum2 - sum1 << endl;
	for (int i = 0; i < ans.size(); i++) {
		if (ans[i].v != n + 1) {
			cout << ans[i].u << ' ' << ans[i].v << endl;;
		}
	}
	return 0;
} 
posted @ 2023-07-20 21:29  forleaves  阅读(38)  评论(0)    收藏  举报