可持久化数据结构和离线算法笔记

主席树

主席树 - 求区间 k 小值

​ 可持续化权值线段树, 用于解决查找历史区间第 k 小数问题。

  • 权值线段树:即每个节点储存权值,可以进行线段树上二分。
  • 查询区间 k 小数,即只要把数按照时间顺序 从 1 ~ n 插入进主席树,然后查询区间 k 小数就变成了在主席树上 r 时间的状态差分 l 时间的状态然后进行线段树上二分即可查找出区间 k 小数。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define debug(x)    std::cerr << #x << '=' << x << "\n"
#define println(...)  std::cerr << format(__VA_ARGS__) << std::endl
const int maxn = 2e5 + 9;

struct node {
    int lson, rson, l , r;
    int sum;
    node() : sum(0), lson(0), rson(0), l(0), r(0)
    {}
};
std::vector<node> t; 
int& ls(int p) { return t[p].lson;}
int& rs(int p) { return t[p].rson;}

int tot = 0;

// 动态开点
int build(int l, int r) {
    int p = tot++;
    if (l == r){ return p;}
    int mid = (l + r) >> 1;
    t[p].lson = build(l, mid);
    t[p].rson = build(mid + 1, r);
    return p;
}

// k 指插入的位置,
// l 时间
int update(int k, int l, int r, int rt) {
    int dr = tot++;
    ls(dr) = ls(rt) , rs(dr) = rs(rt), t[dr].sum = t[rt].sum + 1;
    if(l == r) return dr;
    int mid = (l + r) >> 1;
    if(mid >= k) ls(dr) = update(k, l, mid, ls(dr));
    else rs(dr) = update(k, mid + 1 ,r ,rs(dr));
    return dr;
}

// L 节点, R 节点, l - r 范围 , k 小数
int quiry(int L, int R, int l, int r, int k) {
    int k1 = t[ls(R)].sum - t[ls(L)].sum, mid = l + r >> 1;
    if(l == r) {
        return l;
    }
    if (k1 >= k) return quiry(ls(L), ls(R), l, mid, k);
    else return quiry(rs(L), rs(R), mid + 1, r, k - k1);
    // 权值线段树 - 线段树上二分。
}

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n + 1), b;
    b.reserve(200009);
    b.push_back(-1);
    for(int i = 1; i<= n; i++) std::cin >> a[i], b.push_back(a[i]);

    std::sort(b.begin() + 1, b.end());

    int bl = unique(b.begin() + 1, b.end()) - b.begin();

    auto f = [&](int x) {
        return lower_bound(b.begin() + 1, b.begin() + bl, x) - b.begin();
    };

    std::vector<int> pos;
    pos.push_back(0);
    build(1 ,bl - 1);
    // 其实就是把数据一次一次插入主席树,从 1 - bl - 1 依次插入,
    // 然后求 [l, r] 的第 k 小值, 就转化成利用差分来进行线段树上二分。
    for(int i = 1; i<= n;i++) {
        int x = pos.back();
        pos.push_back(update(f(a[i]),1, bl - 1,x));
        debug(f(a[i]));
    }

    debug(m);
    debug(bl - 1);
    int _l , _r, _k;
    for(int i = 1; i<= m; i++) {
        std::cin >> _l >> _r >> _k;
        std::cout << b[quiry(pos[_l - 1], pos[_r],1, bl - 1, _k)] << "\n";
    }
}

signed main() {
    t.assign(maxn << 5, node());
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#ifdef  CXJY
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    int _ = 1;
    // std::cin >> _;
    while (_--)
        solve();
    return 0;
}

可持续并查集

离线

​ 整体二分(或者回滚莫队)加上可撤销并查集进行回滚。

在线

​ 使用可持久化数组维护并查集,由于不能使用路径压缩,故采用启发式合并,不仅要维护一个 fa ,还要维护一个 size 数组,建树时间复杂度: \(O(n\log n)\) ,查找时间操作 \(O(\log n \cdot \log(n + m))\) ,总空间复杂度 \(O((n + m) \log (n + m))\) ,合并时间复杂度 \(O(\log n \cdot \log {(n+m)} + \log (n +m))\)

可持久化并查集

code
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define debug(x)    std::cerr << #x << '=' << x << "\n"
#define println(...)  std::cerr << format(__VA_ARGS__) << std::endl
#define ls(p)   (t[p].lson)
#define rs(p)   (t[p].rson)

// 对并查集的 fa 数组进行可持续化

struct node {
    int lson, rson, f, sz;
    node() : lson(0), rson(0), f(0), sz(1) {}
};
std::vector<node> t;
std::vector<int> ver;
int n, m;
int tot = 0;

// root = 0
int build(int l, int r) {
    int x = tot++;
    if(l == r) {
        t[x].f = l;
        return x;
    }
    int mid = l + r >> 1;
    ls(x) = build(l, mid);
    rs(x) = build(mid + 1, r);
    return x;
}

// 修改一个版本的数值
int update(int p,int v, int Z, int l, int r, int rt) {
    int x = tot++;
    if(l == r) {
        // debug(x);
        t[x].f = v;
        t[x].sz = Z;
        return x;
    }
    ls(x) = ls(rt), rs(x) = rs(rt);
    int mid = l + r >> 1;
    if(p <= mid)ls(x) = update(p, v, Z, l, mid, ls(x));
    else rs(x) = update(p, v, Z, mid + 1, r, rs(x));
    return x;
}

int quiry(int p, int l, int r, int rt) {
    if(l == r) return rt;
    int mid = l + r >> 1;
    if(p <= mid) return quiry(p, l, mid, ls(rt));
    else return quiry(p, mid + 1, r, rs(rt));
}

int find(int v, int x) {
    int q = quiry(x, 1, n, v);
    int f1 = t[q].f;
    while(x != f1) {
        x = f1;
        q = quiry(x, 1, n, v);
        f1 = t[q].f;
    }
    return q;
}

int merge(int v, int x, int y) {
    int f1 = find(v, x), f2 = find(v, y); 
    if(t[f1].sz > t[f2].sz) std::swap(f1, f2);
    int add = t[f1].sz + t[f2].sz;
    int fa1 = t[f1].f, fa2 = t[f2].f; 
    // debug(fa1), debug(fa2);
    int v1 = update(fa1, fa2, 0, 1, n, v);
    int v2 = update(fa2, fa2, add, 1, n, v1);
    return v2;
}


void solve() {
    std::cin >> n >> m;
    
    t.assign((3 * n + 9) << 5, node());
    
    ver.push_back(build(1, n));
    int op, a, b;
    for(int i = 0; i < m; i++) {
        std::cin >> op;
        if(op == 1) {
            std::cin >> a >> b;
            ver.push_back(merge(ver.back(),a, b));
        } else if(op == 2) {
            std::cin >> a;
            ver.push_back(ver[a]);
        } else {
            std::cin >> a >> b;
            int V = ver.back();
            std::cout << ((find(V, a) == find(V, b)) ? 1 : 0) << "\n";
            ver.push_back(ver.back());
        }
    }

}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#ifdef  CXJY
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    int t = 1;
    // std::cin >> t;
    while (t--)
        solve();
    return 0;
}

整体二分实现删边操作

codeforces - EDU

整体二分,可回退并查集,启发式合并

    #include <bits/stdc++.h>
    using namespace std;
    using i64 = long long;
    #define debug(x)    std::cerr << #x << '=' << x << "\n"
    #define println(...)  std::cerr << format(__VA_ARGS__) << std::endl

    std::vector<int> ans;

    struct edge
    {
        int l, r;
        int u, v;
    };

    class Dsu {
    public:
        
        std::vector<int> fa, siz, st;
        int tp, sum;
        Dsu(int n = 0) : fa(n + 1) , siz(n + 1, 1), st(n + 1), sum(n),tp(0) {
            for(int i = 1; i<= n; i++) fa[i] = i;
        }

        int find(int x) {
            while(x != fa[x]) x = fa[x];
            return x;
        }

        void merge(int x, int y) {
            
            int k1 = find(x) , k2 = find(y);

            if(k1 == k2){
                // st[tp++] = 0;
                return;
            }

            if(siz[k1] > siz[k2]) std::swap(k1, k2);
            siz[k2] += siz[k1];
            st[tp++] = k1;
            // assert(tp < st.size());
            // debug(tp);
            fa[k1] = k2;
            sum--;
        }

        void back() {
            assert(tp > 0); 
            int s1 = st[--tp];
            siz[fa[s1]] -= siz[s1];
            fa[s1] = s1;
            sum++;  
        }
    };

    Dsu S;
    void Bsolve(int l, int r,const std::vector<int> ask,const std::vector<edge> E) {
        if(l == r) {
            int cnt = S.sum;
            for(auto x : E) {
                auto [_l, _r , _u, _v] = x;
                if(_l <= l && r <= _r) {
                    S.merge(_u, _v);
                }
            }
            for(auto x : ask) {
                if(x == l)
                ans[x] = S.sum;
            }
            while(S.sum < cnt) S.back();
            return;
        }

        int mid = (l + r) / 2;

        std::vector<edge> E2;

        // 整体二分,如果覆盖全部区间,并查集就合并

        std::vector<int> ask1, ask2;
        for(auto x : ask) {
            if( x <= mid && x >= l) {
                ask1.push_back(x);
            } else if( x > mid && x <= r) ask2.push_back(x);
        }

        int cnt = S.sum;
        for(auto x : E) {
            auto [_l, _r , _u, _v] = x;
            if(_l <= l && r <= _r) {
                S.merge(_u, _v);
            } else if( _l <= r && l <= _r ) {
                E2.push_back(x);
            }
        }


        
        Bsolve(l, mid, ask1, E2), Bsolve(mid + 1, r, ask2, E2);
        // roll_back
        while(S.sum < cnt) S.back();
        // while(cnt--) S.back();
    }

    void solve() {
        int n, m;
        std::cin >> n >> m;
        ans.assign(m + 1, 0);
        
        std::map<std::pair<int,int>, int> s;
        std::vector<edge> E;
        std::vector<int> Q;

        std::string op;
        int u, v;
        for(int i = 1; i<= m; i++) {
            std::cin >> op ;
            
            if( op == "+" ) {
                std::cin >> u >> v;
                if(u > v) std::swap(u, v);
                s[{u, v}] = E.size();
                E.push_back({i, m, u, v});
            } else if( op == "-") {
                std::cin >> u >> v;
                if(u > v) std::swap(u, v);
                // if(s.find({u,v}) == s.end()) std::swap(u,v);
                int x1 = s[{u,v}];
                E[x1].r = i;
            } else {
                Q.push_back(i);
            }
        }
        if(m > 0) {
            // debug(q);
            S = Dsu(n);
            // 整体二分
            Bsolve(1, m, Q, E);
        
            for(auto x : Q) {
                std::cout << ans[x] << "\n";
            }
        }
    }

    signed main() {
        std::ios::sync_with_stdio(false);
        std::cin.tie(nullptr), std::cout.tie(nullptr);
    #ifdef  CXJY
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif

        int t = 1;
        while (t--)
            solve();
        return 0;
    }
posted @ 2025-03-06 20:27  -风间琉璃-  阅读(27)  评论(0)    收藏  举报