20250814 - 最小生成树 总结
20250814 - 最小生成树
前言
最小生成树可太难了!!!!!!!!!!!!!!
概念
在无向图里选取 \(n-1\) 条边,使这 \(n-1\) 条边和 \(n\) 个点恰好能够组成一颗树,这样的树就被称为原图的生成树。
所以,生成树里边权最小的就是 最小生成树。
方法
1. Dijkstra prim 算法
类似 Dijkstra,都是以点核心的算法,该算法的基本思想是从一个结点开始,不断加点,直到构成一棵树。
具体来说,每次要选择距离最小的一个结点,以及用新的边更新其他结点的距离。
其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,可以暴力找也可以用 \(priority\) _ \(queue\) 维护。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
const int MAXN = 5000 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
void init(){
return;
}
int n,m;
vector<pair<int,int>>e[MAXN];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q; // 太长了!!!!
vector<int>dis,vis;
int prim(){
dis = vector<int>(n + 1,INF);
vis.resize(n + 10);
dis[1] = 0;
q.push({0,1});
int ans = 0,tot = n;
while(!q.empty()){
int u = q.top().second,d = q.top().first;
q.pop();
if(vis[u]) continue;
vis[u] = 1;
ans += d;
tot--;
for(auto y : e[u]){
int v = y.first,w = y.second;
if(w < dis[v]){
dis[v] = w;
q.push({dis[v],v});
}
}
}
if(tot == 0)
return ans;
return -1;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
e[x].push_back({y,w});
e[y].push_back({x,w});
}
int ans = prim();
if(ans == -1)
puts("orz");
else
printf("%d",ans);
return 0;
}
时间复杂度:\(O((n+m)log_2m)\)
注意:千万不要和 Dijkstra 搞混了!!!
kruskal 算法
类似 bellman-ford,都是以边核心的算法,该算法的基本思想是从一条边开始,不断加边,直到构成一棵树。
但如何判断两个点是否在一个集合呢?传递闭包,bfs 并查集!!!
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXH = 2e5 + 7;
const int mod = 1e9 + 7;
int n,m;
struct node{
int x,y,w;
bool operator < (const node &x) const{
return w < x.w;
}
}e[MAXH];
int fa[MAXH];
int find(int x){
if(fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
int kruskal(){
for(int i = 1;i <= n;i++)
fa[i] = i;
sort(e+1,e+m+1);
int tot = n,ans = 0;
for(int i = 1;i <= m;i++){
int x = find(e[i].x),y = find(e[i].y);
if(x != y){
ans += e[i].w;
tot--;
fa[x] = y;
}
}
if(tot != 1)
return -1;
else
return ans;
}
signed main()
{
cin >> n >> m;
for(int i = 1;i <= m;i++){
cin >> e[i].x >> e[i].y >> e[i].w;
}
int ans = kruskal();
if(ans == -1){
cout << "orz" << endl;
}else{
cout << ans << endl;
}
return 0;
}
时间复杂度:\(O(mlog_2m)\)
注意:千万不要和 Bellman-Ford 搞混了!!!```
Kruskal 重构树
在跑 Kruskal 的过程中我们会从小到大加入若干条边。现在我们仍然按照这个顺序。
首先新建
n 个集合,每个集合恰有一个节点,点权为 0。
每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。
不难发现,在进行
n-1 轮之后我们得到了一棵恰有
n 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫 Kruskal 重构树。
例题:P2245 星际导航
Kruskal 重构树板子题,按照定义模拟即可
代码(调了两个半小时):
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
const int MAXN = 4e5 + 7; // 一定要开 2 倍空间
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
int n, m, other_id;
//-----
// 下次还是老老实实写倍增吧
vector<array<int, 3>> edges;
vector<int> e[MAXN];
int dep[MAXN];
int key[MAXN];
int dfn[MAXN], id[MAXN], times, dp[MAXN][20], lg[MAXN], f[MAXN], fa[MAXN];
bool vis[MAXN];
//-----
bool cmp(const array<int, 3>& x, const array<int, 3>& y) {
return x[0] < y[0];
}
int find(int x) {
if (f[x] == x)
return x;
return f[x] = find(f[x]);
}
void dfs(int x) {
dfn[x] = ++times;
id[times] = x;
vis[x] = 1;
for (auto y : e[x]) {
if (!vis[y]) {
fa[y] = x;
dep[y] = dep[x] + 1;
dfs(y);
}
}
}
int MIN(int x, int y) {
return dep[x] < dep[y] ? x : y;
}
void init() {
for (int i = 1; i <= other_id; i++)
dp[i][0] = id[i];
for (int j = 1; (1 << j) <= other_id; j++) {
for (int i = 1; i + (1 << j) - 1 <= other_id; i++) {
dp[i][j] = MIN(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
for (int i = 2; i < MAXN; i++)
lg[i] = lg[i / 2] + 1;
}
int get_lca(int x, int y) {
if (x == y)
return x;
int l = dfn[x], r = dfn[y];
if (l > r) swap(l, r);
l++;
int j = lg[r - l + 1];
return fa[MIN(dp[l][j], dp[r - (1 << j) + 1][j])];
}
int q;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
edges.resize(m + 10);
other_id = n;
for (int i = 1; i <= m; i++) {
int x, y, w;
cin >> x >> y >> w;
edges[i] = {w, x, y};
}
// 一定要开 2 倍空间
for (int i = 1; i <= (n << 1); i++)
f[i] = i;
sort(edges.begin() + 1, edges.begin() + m + 1, cmp); // 排序
for (int i = 1; i <= m; i++) {
int x = find(edges[i][1]), y = find(edges[i][2]);
int w = edges[i][0];
if (x != y) {
f[y] = f[x] = ++other_id;
e[x].push_back(other_id);
e[other_id].push_back(x);
e[y].push_back(other_id);
e[other_id].push_back(y);
key[other_id] = w;
// 奇奇妙妙的建图
}
}
fa[other_id] = other_id;
dep[other_id] = 1;
// 一个坑点:请注意 dfs 时要考虑图不连通的情况,具体来讲就是要对每个连通分量进行一次 dfs(被卡了17次)
for(int i = other_id;i;i--){
if(!vis[i]){
dfs(i);
}
}
init();
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
if (find(x) != find(y))
cout << "impossible" << endl;
else
cout << key[get_lca(x, y)] << endl;
}
return 0;
}