# Disjoint Set Union# DSU# Greedy# Map

f292 - 11987 - Almost Union-Find

🔗 前往 ZeroJudge 原題

題目描述

題目要求實作一個類似於查並集 (Disjoint Set Union) 的資料結構,但增加了移除元素並加入另一個集合的功能。初始時,每個元素都屬於自己的集合。需要支援以下操作:

  1. 合併兩個集合。
  2. 將一個元素從其當前集合移除,並加入另一個集合。
  3. 查詢一個元素所屬集合的大小和元素總和。

解題思路

本題的核心是修改傳統的查並集資料結構以支援移除元素的操作。傳統的查並集僅支援合併集合和查找集合代表元素。為了支援移除操作,我們需要一個額外的機制來追蹤被移除的元素。

程式碼中,pm 是一個 map,用於儲存元素到其當前集合代表元素的映射。fa 陣列儲存每個元素的父節點,用於實現查找操作。sz 陣列儲存每個集合的大小,rk 陣列儲存每個集合中元素的總和。

對於移除操作,我們不會直接修改原始集合,而是將被移除的元素重新映射到一個新的集合,並更新其大小和總和。這樣可以避免修改原始集合的結構,保持查並集操作的效率。

具體來說,當執行移除操作時,我們將元素 x 從其原始集合中移除,並將其映射到一個新的集合 no++。然後,我們更新原始集合的大小和總和,並將新的集合的大小和總和初始化為 x。最後,我們將新的集合與目標集合 y 合併。

複雜度分析

  • 時間複雜度: O(M * α(N)),其中 M 是操作次數,N 是元素的數量,α(N) 是反阿克曼函數,在實際應用中可以視為常數。
  • 空間複雜度: O(N + M),其中 N 是元素的數量,M 是新增集合的數量。pm map 最多會儲存 N + M 個元素。

程式碼

#include <iostream>
#include <map>
using namespace std;
map <long long,long long> pm;
long long fa[300005],sz[300005],rk[300005];
long long n,m,is,x,y,no;
long long ff(long long x){
	if(fa[x]==x)return x;
	return ff(fa[x]);
}
void un(long long x,long long y){
	x=ff(x);
	y=ff(y);
	if(x==y)return;
	if(sz[x]>sz[y]){
		sz[x]+=sz[y];
		rk[x]+=rk[y];
		fa[y]=fa[x];
	}
	else{
		sz[y]+=sz[x];
		rk[y]+=rk[x];
		fa[x]=fa[y];
	}
}
int main(){
	cin.tie(0); cout.tie(0);ios::sync_with_stdio(false);
	while(cin >> n >> m){
		pm.clear();
		no=n+10;
		for(int i=0;i<=n+m+5;++i){
			if(i>n){
				fa[i]=rk[i]=sz[i]=0;
			}
			else{
				pm[i]=fa[i]=rk[i]=i;
				sz[i]=1;
			}
		}
		for(int i=0;i<m;++i){
			cin >> is;
			if(is==1){
				cin >> x >> y; 
				x=pm[x];
				y=pm[y];
				un(x,y);
			}
			else if(is==2){
				cin >> x >> y;
				long long tx=pm[x];
				long long ty=pm[y];
				if(tx==ty)continue;
				sz[ff(tx)]--;
				rk[ff(tx)]-=x;
				pm[x]=no++;
				tx=pm[x];
				sz[tx]=1;
				rk[tx]=x;
				fa[tx]=tx;
				un(tx,pm[y]);
			}
			else{
				cin >> x;
				x=pm[x];
				cout << sz[ff(x)] << " " << rk[ff(x)] << "\n";
			}
		}
		
	}
}

Discussion