[SHOI2014] 神奇化合物 题解

线段树分治,感觉跟模板题很像啊。

如何统计一张无向图的极大联通子图个数?可以直接用并查集维护结点之间的联通关系,统计有多少个集合即可。

但是这道题有删除操作,并查集不能简单地执行这种操作,所以得改变我们的维护方式,尝试把修改和询问离线下来。

一个插入和删除操作正好是配对的,在一条边被插入后直到这条边被删除前,这条边都有可能对答案做出贡献,这启发我们对时间轴做一些操作。

具体地,可以对 $[0, q]$ 这一段时间建一棵线段树,把一条边的加入和删除看作在这棵线段树上区间“加边”,如果某一个区间被覆盖到了就表示在这一段时间里这条边是存在的。

线段树分治就很套路了,进入一个区间的时候把这个区间上存储的边全部加进并查集里面,退出区间的时候撤销掉这些连边操作。因为涉及到撤销,所以并查集不能用路径压缩而是得用按秩合并优化。

统计答案的话就是:连边时如果两个结点不在同一个集合里就将当前极大联通子图个数减 $1$,撤销连边时将当前极大联通子图个数加 $1$。

连边操作用一个栈记录下来即可,撤销时弹栈弹到旧栈顶即可。

附一份代码,有点丑,代码里是把一个连边操作压到了一个 long long 变量里:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, q, u, v, rel;
unordered_map<ll, int> mp;
char op;
struct Segment_Tree {
    int pos, L, R, top, ans, l[40005], r[40005], father[5005], height[5005];
    bool flag[40005];
    ll v;
    pair<int, int> st[40005];
    vector<ll> g[40005];
    #define lc (k << 1)
    #define rc (lc | 1)
    #define mid ((l[k] + r[k]) >> 1)
    Segment_Tree() {
        for(int i = 1; i <= 5000; ++i) father[i] = i, height[i] = 1;
    }
    int findset(int x) {return father[x] == x ? x : findset(father[x]);}
    void merge(int x, int y) {
        if(x == y) return;
        --ans;
        if(height[x] > height[y]) swap(x, y);
        st[++top] = make_pair(x, height[x] == height[y]);
        father[x] = y, height[y] -= (height[x] == height[y]);
    }
    void demerge() {
        ++ans;
        height[father[st[top].first]] -= st[top].second;
        father[st[top].first] = st[top].first;
        --top;
    }
    void build(int k) {
        if(l[k] == r[k]) return;
        l[lc] = l[k], r[lc] = mid, l[rc] = mid + 1, r[rc] = r[k];
        build(lc), build(rc);
    }
    void change(int k) {
        if(L <= l[k] && r[k] <= R) {
            g[k].push_back(v);
            return;
        }
        if(L <= mid) change(lc);
        if(R > mid) change(rc);
    }
    void add(int k) {
        if(l[k] == r[k]) {
            flag[k] = true;
            return;
        }
        if(pos <= mid) add(lc);
        else add(rc);
    }
    void solve(int k) {
        int last = top;
        for(const auto& i : g[k]) {
            merge(findset(i >> 31), findset(i & 2147483647));
        }
        if(l[k] == r[k]) {
            if(flag[k]) cout << ans << '\n';
        }
        else {
            solve(lc), solve(rc);
        }
        while(top > last) demerge();
    }
    void change(int lt, int rt, ll val) {
        L = lt, R = rt, v = val;
        return change(1);
    }
    void Add(int Pos) {
        pos = Pos;
        return add(1);
    }
} tree;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    while(m--) {
        cin >> u >> v;
        if(u > v) swap(u, v);
        rel = (u << 31) | v;
        mp[rel] = 0;
    }
    cin >> q;
    tree.l[1] = 0, tree.r[1] = q;
    tree.build(1);
    for(int i = 1; i <= q; ++i) {
        cin >> op;
        if(op == 'A') {
            cin >> u >> v;
            if(u > v) swap(u, v);
            rel = (u << 31) | v;
            mp[rel] = i;
        }
        else if(op == 'D') {
            cin >> u >> v;
            if(u > v) swap(u, v);
            rel = (u << 31) | v;
            tree.change(mp[rel], i - 1, rel);
            mp.erase(rel);
        }
        else if(op == 'Q') {
            tree.Add(i);
        }
    }
    for(const auto& i : mp) {
        tree.change(i.second, q, i.first);
    }
    tree.ans = n;
    tree.solve(1);
    return 0;
}
posted @   A_box_of_yogurt  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
Document
点击右上角即可分享
微信分享提示