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; }

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/18166627.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示