图论整理——最小生成树篇

  本文主要是本人个人的学习整理,不会倾向于细致的讲解算法本身,仅介绍主要思路和一些个人做过觉得不错的题目的推荐。

最小生成树学习合集

最小生成树

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。(百度百科)个人认为最小生成树从板子和思路上都比较好理解,所以感觉不太可能考裸的最小生成树,更多是作为一种思路出现。

基本解决算法

针对最小生成树(基本的板子题),主要有两种算法 P r i m Prim Prim K r u s k a l Kruskal Kruskal

K r u s k a l Kruskal Kruskal

Kruskal主要是从边的角度出发构造最小生成树。将原图的每条边按边权排序,然后依次取出判断是否和之前的图成环(并查集判断),不成环则加入,否则跳过。复杂度是 O ( e l o g e ) O(eloge) O(eloge),适用于解决疏松图。

#include <bits/stdc++.h>
#define MAXN 5555
#define MAXM 222222
using namespace std;
int n, m, ans;
int fa[MAXN];
struct SF
{
	int x, y, vlu;
}grf[MAXM];
int read()
{
  	int num = 0; bool f = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){f = (ch == '-'); ch = getchar();}
	while(ch >= '0' && ch <= '9'){num = (num << 1) + (num << 3) + ch - '0'; ch = getchar();}
	return f? -num : num;  
} 
int getfa(int s){return fa[s] == s? s : fa[s] = getfa(fa[s]);}
bool cmp(SF a, SF b){return a.vlu < b.vlu;}
void kruskal()
{
	for(int i = 1; i <= m; i++)
	{
		int fx = getfa(grf[i].x), fy = getfa(grf[i].y);
		if(fx != fy) 
		{
			ans += grf[i].vlu;
			fa[fx] = fy;
		}
	}
}
int main()
{
	n = read(); m = read();
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++)
		grf[i].x = read(), grf[i].y = read(), grf[i].vlu = read();
	sort(grf + 1, grf + m + 1, cmp);
	kruskal();
	printf("%d\n", ans);
	return 0;
}

P r i m Prim Prim

  prim主要是从点出发。将原图分为两类,一类 A A A是已加入生成树的点,另一类 B B B是未加入生成树的点,初始时指定任意一点为起点,加入生成树中,每次选择具有最小权值的与A类点相连的B类点加入生成树,直到所有点都被加入生成树。
   P r i m Prim Prim与点的相关性较大,适合解决稠密图的问题(边较多的情况),复杂度为 O ( n 2 ) O(n^2) O(n2),主要思路有点类似于广搜。

#include <bits/stdc++.h>
#define ll long long
#define MAXN 5555
#define MAXM 222222
using namespace std;
int n, m;
int mark[MAXN];
struct SF
{
	int to; int vlu;
	bool operator < (const SF &a) const
	{
		return vlu > a.vlu;
	}
};
vector <SF> grf[MAXN];
priority_queue <SF> q; 
int read()
{
  	int num = 0; bool f = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){f = (ch == '-'); ch = getchar();}
	while(ch >= '0' && ch <= '9'){num = (num << 1) + (num << 3) + ch - '0'; ch = getchar();}
	return f? -num : num;  
} 
int prim()
{
	int ans = 0, cnt = 0;
	q.push((SF){1, 0});
	while(!q.empty() && cnt <= n)
	{
		SF tmp = q.top(); q.pop();
		if(mark[tmp.to]) continue;
		cnt++;
		ans += tmp.vlu; 
		mark[tmp.to] = 1;
		int len = grf[tmp.to].size();
		for(int i = 0; i < len; i++)
			if(!mark[grf[tmp.to][i].to]) q.push((SF) {grf[tmp.to][i].to, grf[tmp.to][i].vlu});
	}
	return ans;	
}
int main()
{
	n = read(), m = read();
	for(int i = 1; i <= m; i++)
	{
		int a = read(), b = read(), c = read();
		grf[a].push_back((SF) {b, c});
		grf[b].push_back((SF) {a, c});
	}
	printf("%d\n", prim());
	return 0;	
} 

个人认为比较适合练习的进阶题

  最小生成树不太可能出什么板子题,一般都会和别的算法相结合,或是作为一种思路出现。

NOIP2013提高组 货车运输

洛谷P1967
P1967题面

题意

  给定一个n个点m条边的无向图,每边有一限重,每次经过的车辆不可超过该限重。有q次询问,每次给定城市x和y,求车辆的最大载重。

思路

P1967数据范围
  看到这个数据范围,首先排除对每个询问跑最长路的做法。为使载重最大,不可能跑限重小的边,而且只需保持xy间联通即可,所以考虑用最大生成树对原图重新建图。
  对于每次询问,在最大生成树上对应的结果为两点之间最小的权值最大,同时在树上的路径使唯一的,仅需经过其公共祖先即可,所以考虑用 l c a lca lca来回答询问。

#include <bits/stdc++.h>
#define re read()
#define ll long long
#define mp(a, b) make_pair(a, b)
#define mst(a, c) memset(a, c, sizeof(a))
#define rep(a, b, c) for(int a = b; a <= c; a++)
#define per(a, b, c) for(int a = b; a >= c; a--)
using namespace std;
int read()
{
	int num = 0; bool f = 0; char ch = getchar();
	while(ch < '0' || ch > '9') {f = (ch == '-'); ch = getchar();}
	while(ch >= '0' && ch <= '9') {num = (num << 1) + (num << 3) + ch - '0'; ch = getchar();}
	return f? -num : num;
}
int n, m, cnt;
const int MAXN = 1e4 + 11, MAXM = 5e4 + 11, INF = 1e9 + 11;
int fa[MAXN], hed[MAXN], dep[MAXN], lfa[MAXN][22], ldis[MAXN][22], mark[MAXN];
struct SF
{
	int to, nxt, vlu;
}grf[MAXM << 1];
struct SFes
{
	int fr, to, vlu;
}edg[MAXM];
void add(int x, int y, int c)
{
	grf[++cnt].to = y; grf[cnt].vlu = c; grf[cnt].nxt = hed[x]; hed[x] = cnt;
	grf[++cnt].to = x; grf[cnt].vlu = c; grf[cnt].nxt = hed[y]; hed[y] = cnt;
}
int getfa(int x) {return fa[x] = fa[x] == x? x : getfa(fa[x]);}
bool cmp(SFes a, SFes b) {return a.vlu > b.vlu;}
void kruskal()
{
	rep(i, 1, n) fa[i] = i;
	sort(edg + 1, edg + m + 1, cmp);
	rep(i, 1, m)
	{
		int fx = getfa(edg[i].fr), fy = getfa(edg[i].to);
		if(fx != fy)
		{
			fa[fx] = fy;
			add(edg[i].fr, edg[i].to, edg[i].vlu);
		}
	} 
}
void dfs(int u)
{
	mark[u] = 1;
	for(int i = hed[u]; i; i = grf[i].nxt)
	{
		int v = grf[i].to;
		if(mark[v]) continue;
		dep[v] = dep[u] + 1, lfa[v][0] = u, ldis[v][0] = grf[i].vlu, dfs(v);
	}
}
int lca(int a, int b)
{
	int res = INF;
	int fx = getfa(a), fy = getfa(b); if(fx != fy) return -1;
	if(dep[a] < dep[b]) swap(a, b);
	per(i, 20, 0) if(dep[b] <= dep[lfa[a][i]]) res = min(res, ldis[a][i]), a = lfa[a][i];
	if(a == b) return res;
	per(i, 20, 0) if(lfa[a][i] != lfa[b][i]) 
	{
		res = min(res, min(ldis[a][i], ldis[b][i]));
		a = lfa[a][i], b = lfa[b][i];
	}
	res = min(res, min(ldis[a][0], ldis[b][0]));
	return res; 
}
int main()
{
	n = re, m = re;
	for(int i = 1; i <= m; edg[i].fr = re, edg[i].to = re, edg[i].vlu = re, i++); kruskal();
	rep(i, 1, n) if(!mark[i]) dep[i] = 1, dfs(i), lfa[i][0] = i, ldis[i][0] = INF;
	for(int j = 1; j <= 20; j++) rep(i, 1, n) 
		lfa[i][j] = lfa[lfa[i][j - 1]][j - 1], ldis[i][j] = min(ldis[i][j - 1], ldis[lfa[i][j - 1]][j - 1]);
	per(_, re, 1)
	{
		int x = re, y = re;
		printf("%d\n", lca(x, y));
	}
	return 0; 
}

TBC…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值