本文主要是本人个人的学习整理,不会倾向于细致的讲解算法本身,仅介绍主要思路和一些个人做过觉得不错的题目的推荐。
最小生成树学习合集
最小生成树
一个有 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提高组 货车运输
题意
给定一个n个点m条边的无向图,每边有一限重,每次经过的车辆不可超过该限重。有q次询问,每次给定城市x和y,求车辆的最大载重。
思路
看到这个数据范围,首先排除对每个询问跑最长路的做法。为使载重最大,不可能跑限重小的边,而且只需保持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…