「JOISC 2020 Day2」有趣的 Joitter 交友

「JOISC 2020 Day2」有趣的 Joitter 交友

传送门

Loj

题解

首先我们考虑,有着双向边的两个点可以合并成为一个点,这是比较显然的(.此时只有单向边在我们的新图里面.

仔细构思一下,不难发现在一条边加入的时候,有如下三种情况:

  1. 两个点已经在一起了,这个时候啥都不用做.
  2. 两个点之间没有任何的关系,那么只需要记个答案就行了.
  3. 两个点之间有着反向连接的关系,这个时候需要重新计算答案然后合并这两个节点.

计算答案考虑一个连通块的贡献是\(siz_u*(siz_u-1)+siz_u*in_u\),其中\(siz\)为这个连通块的大小,\(in\)是入边数量.

唯一需要考虑的点在于可能会影响很多下,要递归合并.

代码

#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define file(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
#define ll long long
#define mp make_pair
#define re register
typedef pair<int,int> pii;
inline int gi()
{
	int f=1,sum=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return f*sum;
}
const int N=600010;
int siz[N],f[N],n,m;ll ans;
set<int>in[N];set<pii>out[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void solve(int u,int v)
{
	int fu=find(u),fv=find(v);
	if(fu==fv||in[fv].find(u)!=in[fv].end())return;
	if(out[fv].lower_bound(mp(fu,0))->first!=fu)
	{
		ans+=siz[fv];
		in[fv].insert(u);
		out[fu].insert(mp(fv,u));
		return;
	}
	if(in[fu].size()+out[fu].size()<in[fv].size()+out[fv].size())swap(fu,fv);
	vector<int>IN;vector<pii>OUT;
	for(auto it:out[fv])
	{
		int _=it.first,__=it.second;
		in[_].erase(__);ans-=siz[_];
		if(_!=fu)OUT.push_back(it);
	}
	out[fv].clear();
	ans+=2ll*siz[fu]*siz[fv]-in[fv].size()*siz[fv]+in[fu].size()*siz[fv];
	for(auto it:in[fv])
	{
		int _=find(it);
		out[_].erase(mp(fv,it));
		if(_!=fu)IN.push_back(it);
	}
	in[fv].clear();
	f[fv]=fu;siz[fu]+=siz[fv];
	for(auto it:IN)solve(it,fu);
	for(auto it:OUT)solve(it.second,it.first);
}
int main()
{
	n=gi();m=gi();
	for(int i=1;i<=n;i++)siz[i]=1,f[i]=i;
	while(m--)
	{
		int u=gi(),v=gi();
		solve(u,v);
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/fexuile/p/12956774.html