可持久化数据结构和离线算法笔记
主席树
主席树 - 求区间 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;
}
任何时候都有比放弃更好的选择。

浙公网安备 33010602011771号