最小生成树(kruskal、prim、最小生成森林问题、严格次小生成树)

本文深入探讨了最小生成树的各种算法,包括kruskal、prim和Boruvka算法,并介绍了如何解决生成森林问题、最小生成树唯一性、严格次小生成树等问题。还涉及了瓶颈生成树、最小瓶颈路的算法,以及在特定场景下的优化方法,如使用LCA优化次小生成树。此外,文章还讨论了Kruskal重构树、最小生成树计数和曼哈顿最小生成树等高级主题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整理的算法模板合集: ACM模板


一、kruskal算法

给出一个无向图,求出最小生成树

typedef pair<int,int> PII;
const int N = 2e5+7;

struct node{
   
   
    int x,y,z;
    bool operator<(node &t)const{
   
   
        return z < t.z;
    }
}edge[N];

int fa[N],n,m,ans;

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

int main()
{
   
   
    cin>>n>>m;
    over(i,1,m)
    scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
    sort(edge + 1,edge + 1 + m);
    over(i,1,n)
    fa[i] = i;
    over(i,1,m){
   
   
        int x = Find(edge[i].x);
        int y = Find(edge[i].y);
        if(x == y)continue;
        fa[x] = y;
        ans += edge[i].z;
    }
    printf("%d\n",ans);
}


二、prim算法

朴素写法,拿n-1条边,建议使用。
有时候普通的版本可以过,但是堆优化版本就会wa,所以尽量用kruskal,实在不行了就用朴素版本。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 507, M = 5000007, INF = 0x3f3f3f3f;

int g[N][N], dist[M], n, m, ans;
bool vis[N];

int prim(){
   
   
    memset(dist, 0x3f, sizeof dist);
    memset(vis, 0, sizeof vis);
    dist[1] = 0;
    int res = 0;
    for(int i = 0;i < n; ++ i){
   
   
        int t = 0;
        for(int j = 1; j <= n; ++ j){
   
   
            if(!vis[j] && (t == 0 || dist[j] < dist[t])){
   
   
                t = j;
            }
        }
            
        if(i && dist[t] == INF)return INF;
        if(i)res += dist[t];
        vis[t] = 1;
        for(int y = 1; y <= n; ++ y){
   
   
            if(!vis[y])dist[y] = min(dist[y], g[t][y]);
        }
    }
    return res;
}

int main(){
   
   
    scanf("%d%d", &n, &m);
    memset(g, 0x3f, sizeof g);
    for(int i = 1; i <= n; ++ i)g[i][i] = 0;
    for(int i = 1; i <= m; ++ i){
   
   
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        g[x][y] = g[y][x] = min(g[x][y], z);
    }

    if(prim() == INF)puts("impossible");//不存在最小生成树
    else {
   
   
        for(int i = 2; i <= n; ++ i)
        ans += dist[i];
        if(ans == INF)puts("impossible");
        else printf("%d\n", ans);
    }
    return 0;
}

堆优化版本。

typedef long long ll;
typedef pair<int,int> PII;
const int N = 4e5+7;

int ver[N],nex[N],edge[N],head[N],tot;
int n,m,ans;
int dis[N];
int vis[N],cnt;
void add(int u,int v,int val){
   
   
    ver[++tot] = v;
    edge[tot] = val;
    nex[tot] = head[u];
    head[u] = tot;
}

priority_queue<PII,vector<PII>,greater<PII> >q;

void prim(){
   
   
    dis[1] = 0;
    q.push({
   
   0,1});
    while(q.size()&&cnt != n){
   
   
        int d = q.top().first,u = q.top().second;
        q.pop();
        if(vis[u])continue;
        cnt++;
        ans += d;
        vis[u] = 1;
        for(int i = head[u];i;i = nex[i]){
   
   
            int v = ver[i];
            if(edge[i] < dis[v])
                dis[v] = edge[i],q.push({
   
   dis[v],v});
        }
    }
}

int main()
{
   
   
    memset(dis,0x3f,sizeof dis);
    scanf("%d%d",&n,&m);
    over(i,1,m){
   
   
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    prim();
    if(cnt == n)
    	printf("%d\n",ans);
    else puts("orz");
    return 0;
}

三、Boruvka算法

有 n 个点排成一行,在第 i,j 个点之间连边的代价为 ∣ i − j ∣ × D + A i + A j |i-j| \times D+A_i+A_j ij×D+Ai+Aj,求将它们连成一棵树的最小代价。( 1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1n105
直接 O ( n 2 ) O(n^2) O(n2)建图会T,而Boruvka算法正适用于此类题目,可以在 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度内解决问题

考虑维护当前的连通块(初始每个点为独立的一个连通块)
对每个连通块,找到一条与该连通块相连的,且另一端点不在此连通块中的边权最小的边。
将所有的这些边都加入最小生成树,注意,当加入一条边时需判断该边的两端点是否在同一连通块。
重复若干遍上述操作,直到图连通。

/*Atcoder keyence2019 E*/
#include <cstdio>
#include <iostream>
#include <cstring>
#define MN 201000

typedef long long ll;
int fa[MN];
int c1[MN], c2[MN];
int Min[MN];
ll D; int a[MN];
int X[MN], Y[MN];
int x[MN], y[MN];
ll ans = 0;
int bl;
int n;

int Abs(int a) {
   
   return a > 0 ? a : -a;}
ll F(int i) {
   
   return i ?  i * D + a[i] : 1e18;}
ll G(int i) {
   
   return i ? -i * D + a[i] : 1e18;}
ll H(int i, int j) {
   
   if(i == 0 || j == 0) return 1e18; return Abs(i - j) * D + a[i] + a[j];}

void add(int *c, int x, int v, int type)
{
   
   
    for(int i = x; i <= n; i += i & -i)
    {
   
   
        if(!type) if(F(v) < F(c[i])) c[i] = v;
        if( type) if(G(v) < G(c[i])) c[i] = v;
    }
}

int query(int *c, int x, int type)
{
   
   
    int ans = 0;
    for(int i = x; i; i -= i & -i)
    {
   
   
        if(!type) if(F(c[i]) < F(ans)) ans = c[i];
        if( type) if(G(c[i]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值