1. 程式人生 > 其它 >Kruskal 演算法 && Kruskal 重構樹

Kruskal 演算法 && Kruskal 重構樹

把別人的經驗變成自己的,他的本事就大了。

Kruskal 演算法

將所有邊按照權值排序,按照權值從低到高遍歷每條邊,維護一個並查集,兩個節點屬於一個集合當且僅當當前這兩個集合在同一個聯通塊當中。

對於每條邊 \((u,v)\)

  • 如果 \(u,v\) 屬於同一個連通塊,那麼跳過;
  • 否則加入這條邊,將 \(u,v\) 所對應的兩個連通塊連線到一起。

複雜度:整體複雜度 \(O(m\alpha+m \log m)\)

練習

洛谷 P5109 [NOI2018] 歸程

Kruskal 重構樹

首先,跑一遍 Dijkstra,得到 \(1\) 號節點到所有節點的最短路。

找到節點 \(m\) ,取其最短路的最小值。

考慮如何計算從一個節點 \(v\)

經過權值不超過 \(a\) 的節點集合。

重新考慮 Kruskal 的過程,在合併並查集過程中,對於每個合併的過程新建一個節點,將兩個子樹的根節點的父親同時設定為這個新節點,此時這個節點的子樹就可以代表這個所有經過不超過某個權值的邊的連通塊。

考慮對於一個節點 \(u\) 和一個權值 \(a\),當沿著其父親節點不斷向上走是,對應邊權值逐漸變大,即恰好會有一個節點 \(m\) 其對應的子樹就是從這個節點出發經過不大於 \(a\) 的邊能到達的所有點,只需取裡面最短路的最小值。

練習

CODEFORCES 722C Destroying Array

倒序操作。

程式碼

#include<bits/stdc++.h>
#define N 100100
#define ll long long int
using namespace std;

bool sign[N];
int n;
ll f[N],del[N],num[N],c[N],ans[N];
inline int FIND(int);

int main()
{
    memset(c,0,sizeof(c));
    memset(num,0,sizeof(num));
    memset(f,0,sizeof(f));
    memset(del,0,sizeof(del));
    memset(ans,0,sizeof(ans));
    memset(sign,false,sizeof(sign));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&num[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&del[i]);
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=n,x;i>1;i--)
    {
        x=del[i];
        sign[x]=true;
        ll res=0;
        if(sign[x-1])
            res+=c[FIND(x-1)],f[FIND(x-1)]=x;
        if(sign[x+1])
            res+=c[FIND(x+1)],f[FIND(x+1)]=x;
        res+=num[x];
        c[x]=res;
        ans[i]=max(ans[i+1],res);
    }
    for(int i=2;i<=n;i++)
        printf("%lld\n",ans[i]);
    puts("0");
    return 0;
}

inline int FIND(int x)
{
	return f[x]==x?x:f[x]=FIND(f[x]);
}

洛谷 P4092 [HEOI2016/TJOI2016]樹

寫於 2021年7月6日 在 焦作一中 集訓中