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;
}
posted @ 2024-04-29 20:46  maple276  阅读(41)  评论(0编辑  收藏  举报