【JOISC 2020】【LOJ3257】【UOJ505】有趣的 Joitter 交友(启发式合并)

题解:

结论很显然,双向连接的连通块会连成完全图,单向的会从前面向后连完整个连通块。

维护一下那些连通块之间有边,哪些点连向了自己,合并启发式合并即可,注意连环合并的情况。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}template<typename T>T get_integer(){
		char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
		while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
	}inline int gi(){return get_integer<int>();}
}using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e5+7;

int n,m;
ll ans;

int fa[N],sz[N];
inline int gf(int x){
	while(x!=fa[x])x=fa[x]=fa[fa[x]];
	return x;
}

std::set<int> to[N],fr[N];
std::set<int> fr_nd[N];
std::vector<int> nd[N];

void merge_nd(int u,int v){
	for(int w:nd[v]){
		nd[u].push_back(w);
		fr_nd[u].erase(w);
	}nd[v].clear();
	for(int w:fr_nd[v])
		if(gf(w)!=u)fr_nd[u].insert(w);
	fr_nd[v].clear();sz[u]+=sz[v];
}

void find_cap(cs std::set<int> &A,cs std::set<int> &B,std::queue<int> &q){
	for(int u:A)if(B.find(u)!=B.end())q.push(u);
}

void merge_bl(int u,int v){
	for(int a:fr[v])if(a!=u){
		to[a].erase(v);
		to[a].insert(u);
		fr[u].insert(a);
	}
	for(int a:to[v])if(a!=u){
		fr[a].erase(v);
		fr[a].insert(u);
		to[u].insert(a);
	}fr[u].erase(v);to[u].erase(v);
	fr[v].clear(),to[v].clear();
}

void merge_block(int a,int b){
	std::queue<int> q;q.push(a),q.push(b);
	while(!q.empty()){
		int u=q.front();q.pop();
		if(q.empty())break;
		int v=q.front();q.pop();
		u=gf(u),v=gf(v);
		if(u==v){q.push(u);continue;}
		ans-=(sz[u]-1ll)*sz[u]+(sz[v]-1ll)*sz[v];
		if(sz[u]<sz[v])std::swap(u,v);
		ans-=sz[u]*(ll)fr_nd[u].size();
		ans-=sz[v]*(ll)fr_nd[v].size();
		fa[gf(v)]=gf(u);merge_nd(u,v);
		find_cap(fr[v],to[u],q);
		find_cap(to[v],fr[u],q);
		merge_bl(u,v);q.push(u);
		ans+=(sz[u]-1ll)*sz[u];
		ans+=sz[u]*(ll)fr_nd[u].size();
	}
}

void add_edge(int u,int A){
	if(fr_nd[A].find(u)==fr_nd[A].end()){
		fr_nd[A].insert(u),ans+=sz[A];
		fr[A].insert(gf(u));
		to[gf(u)].insert(A);
	}
}

void Main(){
	n=gi();m=gi();
	for(int i=1;i<=n;++i){
		fa[i]=i,sz[i]=1;
		nd[i].push_back(i);
	}
	while(m--){
		int a=gi(),b=gi();
		int A=gf(a),B=gf(b);
		if(A==B){
			cout<<ans<<"\n";
			continue;
		}
		if(to[B].find(A)!=to[B].end())
			merge_block(A,B);
		else add_edge(a,B);
		cout<<ans<<"\n";
	}
}

inline void file(){
#ifdef zxyoi
	freopen("joitter.in","r",stdin);
#endif
}signed main(){file();Main();return 0;} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值