主席树
主席树 - 求区间 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 CXJYfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endifint _ = 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 CXJYfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endifint 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::endlstd::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_backwhile(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 CXJYfreopen("in.txt","r",stdin);freopen("out.txt","w",stdout);#endifint t = 1;while (t--)solve();return 0;}