CF1140F 题解(线段树分治+可撤销并查集)
CF1140F:
题意: 集合 S 中存放点集(x,y),拓展集合 S' :(a,b) (x,b) (a,y) 在 S 中,则 (x,y) 在 S'。每次往 S 中添加一个点或删除一个点,求此时 S' 大小。
Solution:
前置知识:线段树分治+可撤销并查集,注意这是一个科技,不是两个。
一般看到形如 \((x,y)\) 的点对大家都会把它理解为 x 指向 y 的边,但这题貌似这么连边还是摸不到头脑。
这题对新边的拓展并不是一个环,而是有个类似方向性的拓展方式,a 指向 b 和 y,x 也指向 b 和 y。手玩时发现每当加入 \((x,y)\) 这条边,则 “指向 y 的点” 也会指向 “ x 指向的点”,貌似每个点需要维护两个点集,一个是指向它的点,一个是它指向的点。
而维护两个点集是很麻烦的事情,但是我们可以拆点。把 x 拆成两个点:x 和 x+n。a 连向 b 的边表示 a 和 b+n 之间的无向边,你会发现连边关系变成了一张二分图,而扩展的含义也就是二分图所有连通块内,左侧点全部指向右侧点。
经过了这个关键的题意转化后,我们就可以套用 线段树分治+可撤销并查集 的科技,需要套用线段树是因为这题中边是可以撤销的,每条边有个存在的时间段。
我们可以用并查集维护每个连通块内左侧点的数量和右侧点的数量,加边与删边时实时更新答案。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR() ll le=e[u].size();for(ll i=0;i<le;i++)
#define QWQ cout<<"QwQ\n";
#define ll long long
#define PA pair<ll,ll>
#include <vector>
#include <queue>
#include <map>
#define ls now<<1
#define rs now<<1|1
using namespace std;
const ll N=601010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
inline ll read() {
ll sum = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); }
return sum * ff;
}
ll n,da = 300000;
ll fa[N];
map < PA , ll > f;
vector <PA> t[N<<2];
ll siz1[N],siz2[N],dep[N];
struct CZ{
ll x,y,depy,tim;
}st[N];
ll cnt;
ll ans;
ll find(ll x) { return fa[x]==x ? x : find(fa[x]); }
void insert(ll now,ll l,ll r,ll x,ll y,PA pa) {
// if(now==1) cout<<"insert "<<x<<","<<y<<" "<<pa.first<<" -> "<<pa.second<<"\n";
if(x<=l && r<=y) { t[now].push_back(pa); return ; }
ll mid = l+r >> 1;
if(x<=mid) insert(ls, l, mid, x, y, pa);
if(y>mid) insert(rs, mid+1, r, x, y, pa);
}
void merge(ll x,ll y,ll tim) {
x = find(x), y = find(y);
if(x==y) return ;
if(dep[x]>dep[y]) swap(x,y);
st[++cnt] = { x, y, dep[y], tim };
ans -= siz1[y] * siz2[y];
ans -= siz1[x] * siz2[x];
siz1[y] += siz1[x];
siz2[y] += siz2[x];
ans += siz1[y] * siz2[y];
dep[y] += (dep[x]==dep[y]);
fa[x] = y;
}
void undo(CZ cz) {
ll x = cz.x, y = cz.y;
ans -= siz1[y] * siz2[y];
siz1[y] -= siz1[x];
siz2[y] -= siz2[x];
dep[y] = cz.depy;
ans += siz1[y] * siz2[y];
ans += siz1[x] * siz2[x];
fa[x] = x;
}
void DFS(ll now,ll l,ll r) {
for(auto v : t[now]) {
merge(v.first, v.second+da, now);
}
if(l==r) {
cout<<ans<<" ";
}
else {
ll mid = l+r >> 1;
DFS(ls, l, mid);
DFS(rs, mid+1, r);
}
while(st[cnt].tim==now) {
undo(st[cnt--]);
}
}
int main() {
ll x,y;
n = read();
for(ll i=1;i<=n;i++) {
x = read(); y = read();
if(f[ pair<ll,ll>{x,y} ] > 0) {
insert(1, 1, n, f[ PA{x,y} ], i-1, PA{x,y});
f.erase( f.find( PA{x,y} ));
}
else f[ PA{x,y} ] = i;
}
for(auto v : f) {
insert(1, 1, n, v.second, n, v.first);
}
for(ll i=1;i<=2*da;i++) {
fa[i] = i;
if(i<=da) siz1[i] = 1;
else siz2[i] = 1;
}
DFS(1, 1, n);
return 0;
}