Kruskal 重構樹

superl61發表於2024-09-22

演算法介紹

Kruskal 重構樹用於快速判斷節點的連通性.

考慮到,假如兩個節點是聯通的,則他們之間總會有一條邊被選入最小生成樹內,因此他們在最小生成樹內也是聯通的. 也就是說,我們可以透過求最小生成樹來減少我們判斷聯通需要的邊數.

Kruskal 重構樹的思想是這樣的:假如有一條生成樹邊 \((x,y)\),則斷開 \((x,y)\),新建一個虛電 \(i\),連線 \((x,i),(y,i)\),在這樣操作後,因為原生成樹即為一棵樹,因此新構成的圖也是一棵樹,並且樹上任意一點的子樹中的所有節點總是可達的. 這個操作可以在求生成樹時同時進行.

對於帶權的邊,我們可以把邊權直接放到點上. 注意到這樣構造出來的重構樹一定是二叉堆. 並且任意兩點路徑邊權最大值為重構樹上LCA的點權.

為什麼是最大值?考慮到我們構建最小生成樹的過程,最大的邊一定會後選. 在考慮我們構建重構樹的過程,後選的邊總是作為新的父節點,因此兩點的 LCA 即為兩點間路徑中最後選的那一個,即邊權最大值.

注意到這一塊可能是需要爆改的,改這個主要使用改最小生成樹來實現,學長說跑什麼生成樹是需要變通的,比如要求最小邊權就需要跑最大生成樹,有時候還需要跑特殊生成樹,只不過這個演算法的思想是藉助的 Kruskal 的,所以我們用最小生成樹當例子.

首先新建 \(n\) 個集合,每個集合恰有一個節點,點權為 \(0\)

每一次加邊會合並兩個集合,我們可以新建一個點,點權為加入邊的邊權,同時將兩個集合的根節點分別設為新建點的左兒子和右兒子。然後我們將兩個集合和新建點合併成一個集合。將新建點設為根

此處模板使用 \(\gt n\) 的點作為虛點.

void Kruskal(){
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	int cnt=0;
	sort(e+1,e+n+1);
	for(int i=1;i<=m;i++){
    	int x=e[i].x, y=e[i].y, w=e[i].w;
    	int fx=find(x),fy=find(y);
    	if(fx^fy){
        	val[++cnt+n]=w;
			fa[fx]=fa[fy]=fa[cnt+n]=now;
        	e[cnt+n].push_back({fx,1});
        	e[cnt+n].push_back({fy,1});
    	}
	}
}

CF1706E Qpwoeirut and Vertices

這個題涉及到了重構樹求最值的問題,寫了比較有啟發意義的爆改線段樹

此題可以將時間戳設計成點權,考慮到重構樹的特殊性質,我們可以將其按時間戳全部聯通,根據任意兩點路徑邊權最大值為重構樹上LCA的點權,此題我們需要求的正好就是邊權最大值,因此直接維護樹上 LCA 即可. 取最值可以用 st 表或線段樹.

這裡沒排序是因為時間戳是有序的,用的線段樹來維護,更新子節點節點最大權值的時候也可以直接 LCA 解決,比較方便,只是常數比 LCA 大.

#include<bits/stdc++.h>
using namespace std;
int n,m,q,tot;
namespace dsu{
	int fa[300001];
	inline void clear(){
		for(register int i=1;i<=n+m;++i){
			fa[i]=i;
		}
	}
	inline int find(int id){
		if(id==fa[id]) return id;
		fa[id]=find(fa[id]);
		return fa[id]; 
	}
}
vector<int>e[300001];
namespace lca{
	int fa[20][300001],deep[300001];
	inline void dfs(int now){
		for(int i=1;i<=19;++i){
			fa[i][now]=fa[i-1][fa[i-1][now]];
		}
		for(int i:e[now]){
			deep[i]=deep[now]+1;
			fa[0][i]=now;
			dfs(i);
		}
	}
	inline void prework(){
		for(register int i=1;i<=19;++i){
			for(register int j=1;j<=n+m;++j){
				fa[i][j]=fa[i-1][fa[i-1][j]];
			}
		}
	}
	inline int lca(int x,int y){
		if(deep[x]<deep[y]) swap(x,y);
		for(register int i=19;i>=0;--i){
			if((deep[x]-deep[y])>=(1ll<<i)){
				x=fa[i][x];
			}
		}
		if(x==y) return x;
		for(register int i=19;i>=0;--i){
			if(fa[i][x]!=fa[i][y]){
				x=fa[i][x];y=fa[i][y];
			}
		}
		return x==y?x:fa[0][x];
	}
}
namespace stree{
	#define tol (id*2)
	#define tor (id*2+1)
	#define mid(x,y) mid=((x)+(y))/2
	struct tree{
		int l,r,w;
	}t[400001];
	inline void build(int id,int l,int r){
		t[id].l=l,t[id].r=r;if(l==r){
			t[id].w=l;
			return;
		}
		int mid(l,r);
		build(tol,l,mid);
		build(tor,mid+1,r);
		t[id].w=lca::lca(t[tol].w,t[tor].w);
	}
	inline int ask(int id,int l,int r){
		if(t[id].l==l and t[id].r==r) return t[id].w;
		int mid(t[id].l,t[id].r);
		if(r<=mid) return ask(tol,l,r);
		if(l>=mid+1) return ask(tor,l,r);
		return lca::lca(ask(tol,l,mid),ask(tor,mid+1,r));
	}
}
namespace hdk{
    namespace Iter{
		void cout(std::vector<int> &_v,int _from,int _to,char _devide){std::vector<int>::iterator iter;std::vector<int>::reverse_iterator riter;
		if(_from<_to){for(iter=_v.begin()+_from;iter!=_v.begin()+_to;++iter){std::cout<<*iter<<_devide;}}
		else{for(riter=_v.rbegin()+_to;riter!=_v.rbegin()+_from;++riter){std::cout<<*riter<<_devide;}}}
	}
	template<typename T>
	void memset(T a[],int _val,int _size){if(_val==0){for(T* i=a;i<=a+_size-1;++i) *i&=0;return;}for(T* i=a;i<=a+_size-1;++i)*i=_val;}
	namespace fastio{
		void rule(bool setting=false){std::ios::sync_with_stdio(setting);}
		inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
	    inline int read(int &A){A=read();return A;}
        inline char read(char &A){A=getchar();return A;}
		inline void write(int A){if(A<0){putchar('-');A=-A;}if(A>9){write(A/10);}putchar(A%10+'0');}
        inline void write(long long A){if(A<0){putchar('-');A=-A;}if(A>9){write(A/10);}putchar(A%10+'0');}
		inline void write(char A){putchar(A);}
		inline void space(){putchar(' ');}
		inline void endl(){putchar('\n');}
		#define w(a) write(a)
		#define we(a) write(a);endl()
		#define ws(a) write(a);space()
	}
}
using namespace hdk::fastio;
int main(){
//	freopen("lagrange1.in","r",stdin);
//	freopen("hdk.out","w",stdout);
	int cases;read(cases);while(cases--){
		read(n);read(m);read(q);
		tot=0;
		dsu::clear();
		for(int i=1;i<=n+m;++i){
			e[i].clear();
		}
		for(register int i=1;i<=m;++i){
			int x,y;read(x);read(y);
			int fx=dsu::find(x),fy=dsu::find(y);
			if(fx==fy) continue;
			e[i+n].push_back(fx);
			e[i+n].push_back(fy);
			dsu::fa[fx]=dsu::fa[fy]=i+n;
			tot=i+n;
		}
		lca::deep[tot]=1;
		lca::dfs(tot);
	//	lca::prework();
		stree::build(1,1,n);
		for(register int i=1;i<=q;++i){
			int l,r;l=read();r=read();
			if(l^r){
				ws(stree::ask(1,l,r)-n);
			}
			else{
				putchar('0');putchar(' ');
			}
		}
		endl();
	}
}

[NOI2018] 歸程

正在寫,一會發

相關文章