【题解】「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