Soso 的並查集寫掛了

Yorg發表於2024-10-20

題面

似乎有原題, 但是很偏
掛個 pdf
題面下載

演算法

暴力

很顯然, 只需要在並查集維護時稍微加上一點細節

#include <cstdio>
using namespace std;
int n,m,fa[500010],a[500010];
long long ans=0;
int find(int x){
  	ans+=a[x];
	ans%=998244353;
	if(fa[x]==x) return x;
	return find(fa[x]);
}
void merge(int x,int y){
	int tx=find(x);
  	int ty=find(y);
  	fa[ty]=tx;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		fa[i]=i;
	}
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		merge(x,y);
	}
	printf("%lld\n",ans);
	return 0;
}

正解

觀察到 TLE 的原因是每次查詢使用了大量時間, 考慮最佳化

帶權並查集

並查集的優點是能夠透過路徑壓縮最佳化複雜度

維護邊權

這是簡單的, 只需要稍稍修改一下 \(\rm{find}\) 函式即可 (其中 \(d\) 儲存到根的邊權之和)

inline int find(int x) {
	if(fa[x] == x) return x;
	int root = find(fa[x]); //注意一下寫法,先將find(fa[x])存放在root中,否則會出錯 
	d[x] += d[fa[x]];
	return fa[x] = root;
}

維護點權

按照維護邊權的方法寫完之後發現會出問題
觀察到本質上是因為每一次路徑壓縮都會重複計算最上層的根節點
於是想辦法消除其的影響

程式碼(後補)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int n,m;
int a[500010];
pair<int,int> fa[500010];
int ans;
void init(){
	for(int i=1;i<=n;i++){
		fa[i]={i,a[i]};
	}
	return;
}
pair<int,int> find(int x){
	if(fa[x].first==x)return fa[x];
	auto t=find(fa[x].first);
	t.second+=fa[x].second;
	fa[x].second=t.second-fa[t.first].second; 
	fa[x].first=t.first;
	fa[x].second%=mod;
	t.second%=mod;
	return t;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	init();
	while(m--){
		int x,y;
		cin>>x>>y;
		auto fx=find(x),fy=find(y);
		ans+=fx.second+fy.second;
		if(fx.first!=fy.first){
			fa[fy.first].first=fx.first;
		}
	}
	cout<<ans%mod;
	return 0; 
} 

樹上差分

觀察到查詢操作在最終的樹上都是一條鏈, 考慮並查集 + 建樹, 維護樹上差分操作

#include <cstdio>
#include <vector>
#include <cstring>
#include <numeric>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=998244353;
vector<int> g[1<<19];
template<int N> struct dsu{
	int fa[N+10],cnt;
	explicit dsu(int n=N):cnt(n){iota(fa+1,fa+n+1,1);}
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
	void merge(int x,int y){if(x=find(x),y=find(y),x!=y) cnt--,fa[y]=x,g[x].push_back(y);}
};
int n,m,cnt[1<<19],a[1<<19];
dsu<1<<19> s;
LL ans=0;
void dfs(int u){
	for(int v:g[u]) dfs(v),cnt[u]+=cnt[v];
	ans=(ans+1ll*cnt[u]*a[u])%P;
}
int main(){
//	#ifdef LOCAL
//	 	freopen("input.in","r",stdin);
//	#endif
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		cnt[u]++,cnt[v]++;
		cnt[s.find(u)]--,cnt[s.find(v)]--;
		ans=(ans+a[s.find(u)]+a[s.find(v)])%P;
		s.merge(u,v);
	}
	for(int i=1;i<=n;i++) if(s.find(i)==i) dfs(i);
	printf("%lld\n",ans);
	return 0;
}

總結

考場上並沒有想到怎麼維護點權, 自己推的能力需要加強
轉化能力是重要的

相關文章