最小生成树

一个定理:必定包含权值最小的边
不然把小边加入,删除环上任何一条边结果都更优
两个算法:

  1. Kruskal
    枚举边判断点
    贪心算法,每次判断最短的边加入是否会形成环,用并查集判断
    可以加就加入

时间复杂度\(O(mlogm)\)

const int N = 200010 ;
const int M = 500010 ;


int n, m ;
int fa[N] ;

struct edge {
	int x, y, z ;
	friend bool operator < (const edge &a, const edge &b) {
		return a.z < b.z ;
	}
} a[M] ;

int get(int x) {
	if (x == fa[x]) return x ;
	return fa[x] = get(fa[x]) ;
}

void merge(int x, int y) {
	fa[x] = y ;
}
int main() {
	scanf("%d%d", &n, &m) ;
	for (int i = 1; i <= m; i++) scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z) ;
	sort(a + 1, a + m + 1) ;
	for (int i = 1; i <= n; i++) fa[i] = i ;
	ll ans = 0 ;
	for (int i = 1; i <= m; i++) {
		int x = get(a[i].x), y = get(a[i].y) ;
		if (x == y) continue ;
		merge(x, y) ;
		ans += a[i].z ;
	}
	printf("%lld\n", ans) ;
}

  1. Prim

适用于完全图
时间复杂度\(O(N^2)\)
维护点加入边
维护两个集合\(S\)\(T\),一个是已经在树中的,还有一个是还没加入的
加边时只加入一个点在\(S\)中,一个点在\(T\)中这样的边,确保没有环出现
实际操作时是 使新加入\(S\)中的点\(x\),去更新还在\(T\)中的点\(y\)的权值(用边权)
利用标记分开\(S\)\(T\)两个部分

int n, m ;
int a[N][N], d[N], v[N] ;

void prim() {
	memset(d, 0x3f, sizeof(d)) ;
	memset(v, 0, sizeof(v)) ;
	d[1] = 0 ;
	for (int rnd = 1; rnd < n; rnd++) {
		int x = 0 ;
		for (int i = 1; i <= n; i++) if (!v[i] && (x == 0 || d[x] > d[i])) x = i ;
		v[x] = 1 ;
		for (int y = 1; y <= n; y++) if (!v[y]) d[y] = min(d[y], a[y][x]) ;
	}
}

int main() {
	scanf("%d%d", &n, &m) ;
	memset(a, 0x3f, sizeof(a)) ;
	for (int i = 1; i <= m; i++) {
		int x, y, z ; scanf("%d%d%d", &x, &y, &z) ;
		a[x][y] = a[y][x] = min(a[x][y], z) ;
	} 
	prim() ;
	ll ans = 0 ;
	for (int i = 1; i <= n; i++) ans += d[i] ;
	printf("%lld\n", ans) ;
}

例题

走廊泼水节

link
模拟\(kruskal\)加边的方式
\(kruskal\)每次取出最短的边,把两个节点所在的树连接在一起
设子树\(1\)的大小为\(s1\),子树\(2\)的大小为\(s2\)
对于所有子树\(1\)中的一个节点\(s\)和子树\(2\)中的一个节点\(t\),连接\(s\)\(t\)的边的长度必须要长于 当前这条边的长度\((val)\)
这样的边数总共有\(s1*s2-1\)条,边长\(val+1\)
而且\(val+1\)已经可以取到的最短的长度(后面加的边不会影响\(1\)棵子树内部的结果)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std ;

typedef long long ll ;
const int N = 1000010 ;

struct Edge {
	int x, y, val ;
	friend bool operator < (const Edge &a, const Edge &b) {
		return a.val < b.val ;
	}
} edge[N] ;  

int n, T ;
ll ans = 0 ;
int fa[N], sze[N] ;

void init() {
	for (int i = 1; i <= n; i++) fa[i] = i ;
	for (int i = 1; i <= n; i++) sze[i] = 1 ;
	ans = 0 ;
}

int get(int x) {
	if (x == fa[x]) return x ;
	return fa[x] = get(fa[x]) ; 
}

void merge(int x, int y) {
	fa[x] = y ;
	sze[y] += sze[x] ;
}
 
int main() {
	scanf("%d", &T) ;
	while (T--) {
		scanf("%d", &n) ;
		for (int i = 1; i < n; i++) scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].val) ;
		init() ;
		sort(edge + 1, edge + n) ;
		for (int i = 1; i < n; i++) {
			int x = edge[i].x, y = edge[i].y, val = edge[i].val ;
			int fx = get(x), fy = get(y) ;
			ans += 1ll * (val + 1) * (sze[fx] * sze[fy] - 1) ;
			merge(fx, fy) ;
		}
		printf("%lld\n", ans) ; 
	} 
}

posted @ 2023-09-10 11:50  哈奇莱特  阅读(24)  评论(0)    收藏  举报