欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【题解】「JOISC 2020 Day2」有趣的 Joitter 交友

程序员文章站 2024-03-19 08:46:34
...

solution:

部分分好少。

若是有双向连边,那么双向边联通的集合可以视为一个集合。

对于一个大小为 s 的极大团 T 对答案的贡献为:

  • T 内部有 s(s-1) 条边
  • 若有 k 个不同的节点连向 T ,有 sk 条边

in[u] : 入边,连向它的点的集合

out[u] :出边,第一维是所指向节点根编号,第二维是起点编号

  • 如果是 u ,v 的边,应该删除
  • 如果起点是 v,那么应该加入 out[u]
  • 如果终点是 v,那么应该加入 in

咱一码归一码。进行若干次 加边-合并 操作 ,再把合并后影响到的边重新 push 。因为每次合并操作和当前局势密切相关,所以必须按顺序来。这恰好是队列先进先出的原则。

这个代码写起来真 tm 恶心。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define pi pair<int,int>
#define fi first
#define se second
using namespace std;
const int mx=1e5+5;
int n,m,fa[mx],siz[mx];
ll res;
set<int> in[mx];
set<pi> out[mx];
//pi <所指向节点根编号,实际标号>
int find(int x) {
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
ll qry(int u) {
	return 1ll*siz[u]*(siz[u]-1)+(ll)in[u].size()*siz[u];
}
void merge(int x,int y) {
	queue<pi> q; q.push(make_pair(x,y));
	while(q.size()) {
		pi t=q.front(); q.pop();
		int x=t.fi,y=t.se; 
		int u=find(x),v=find(y); if(u==v||in[v].count(x)) continue;
		auto it=out[v].lower_bound(make_pair(u,0));
		if(it==out[v].end()||it->fi!=u) {
			in[v].insert(x);
			out[u].insert(make_pair(v,x));
			res+=siz[v];
			continue;
		}
		res-=qry(u)+qry(v);
		//大致估计时间复杂度和所连边的条数相关 
		if(in[u].size()+out[u].size()<in[v].size()+out[v].size()) {
			swap(u,v),swap(x,y);
		}
		for(auto t:out[v]) {
			in[t.fi].erase(t.se);
			if(t.fi!=u) {
				q.push(make_pair(t.se,t.fi));
				res-=siz[t.fi];
			}
			
		}
		for(auto t:in[v]) {
			int z=find(t);
			out[z].erase(make_pair(v,t));
			if(z!=u) q.push(make_pair(t,u));
		}
		in[v].clear(),out[v].clear();
		fa[v]=u,siz[u]+=siz[v];
		res+=qry(u);
	}
}
signed main() {
//    freopen("data.in","r",stdin); 
//    freopen("own.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
    for(int i=1;i<=m;i++) {
    	int u,v; scanf("%d%d",&u,&v);
        merge(u,v); printf("%lld\n",res);
	}
}
相关标签: 数据结构

上一篇: UVA489 Hangman Judge

下一篇: