UOJ505 【JOISC2020】有趣的Joitter交友
UOJ505 【JOISC2020】有趣的Joitter交友
启发式合并
注意到如果两个点能够互相到达,那么向其中任意一点有连边的必然会与另一个点有连边,所以入度集合是需要合并的。
但是出度集合并不会受影响,那么我们可以对于每个极大的任意两点可以两两到达的连通块,维护这个连通块的点集\(s\),向该连通块有连边的点集\(ip\),向该连通块有连边的连通块编号集合\(in\),该连通块出边连向的连通块编号集合\(out\),这四类集合能够帮助我们维护信息和及时合并。
对于一条边,我们在该边指向的位置统计贡献。容易发现一个集合\(U\)的贡献为\(|s_U|(|s_U-1|)+|s_U||ip_U|\)。
对于合并来说,我们只需要开一个队列来维护即可。
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#define pr pair<int,int>
#define mp make_pair
#define ll long long
#define IT set<int> :: iterator
#define N 100005
using namespace std;
int n,m,x,y,f[N];
set<int>s[N],in[N],out[N],ip[N];
queue< pr >q;
ll ans;
int getf(int x)
{
return (f[x]==x)?x:(f[x]=getf(f[x]));
}
ll calc(int x)
{
return (ll)s[x].size()*(s[x].size()-1)+(ll)s[x].size()*ip[x].size();
}
void combine(int x,int y)
{
ans-=calc(x),ans-=calc(y);
if (s[x].size()<s[y].size())
swap(x,y);
f[y]=x;
for (IT it=s[y].begin();it!=s[y].end();++it)
{
int u(*it);
ip[x].erase(u);
s[x].insert(u);
}
for (IT it=ip[y].begin();it!=ip[y].end();++it)
{
int u(*it);
if (!s[x].count(u))
ip[x].insert(u);
}
for (IT it=in[y].begin();it!=in[y].end();++it)
{
int u(*it);
if (u==x)
continue;
in[x].insert(u),out[u].erase(u),out[u].insert(x);
if (in[u].count(x))
q.push(mp(x,u));
}
for (IT it=out[y].begin();it!=out[y].end();++it)
{
int u(*it);
if (u==x)
continue;
out[x].insert(u),in[u].erase(y),in[u].insert(x);
if (out[u].count(x))
q.push(mp(x,u));
}
ans+=calc(x);
}
void solve()
{
while (!q.empty())
{
int x(getf(q.front().first)),y(getf(q.front().second));
q.pop();
if (x==y)
continue;
combine(x,y);
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
f[i]=i,s[i].insert(i);
for (int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
int fx(getf(x)),fy(getf(y));
if (fx==fy)
; else
if (in[fx].count(fy))
q.push(mp(x,y)),solve();else
{
ans-=calc(fx),ans-=calc(fy);
out[fx].insert(fy),in[fy].insert(fx),ip[fy].insert(x);
ans+=calc(fx),ans+=calc(fy);
}
printf("%lld\n",ans);
}
return 0;
}