主席樹(親媽級註釋)
阿新 • • 發佈:2020-08-08
動態主席樹
本文介紹動態主席樹,學習之前,你必須需要先了解靜態主席樹,本文只介紹相對於靜態主席樹多出來的部分。
動態主席樹已經算是另一種資料結構了。這裡以ZOJ2112為例。
首先我們需要知道主席樹是一種離線資料結構,意思是我們不能一邊詢問一邊修改或輸出。所以我們把詢問儲存起來,這裡主要是因為我們要儲存改變的值,不然無法離散化,所以就離線了。這裡要改變一個值,例如把2變成6,那麼2的數量就要少一個,6的數量就要多一個,可我們又不能再開一個線段樹,所以我們考慮將每一個節點都變成一棵樹,每對他進行操作時就是對這棵樹進行操作。下面給出詳細程式碼及其註釋。
#pragma GCC optimize(2) #include <algorithm> #include <iostream> #include <cstdio> #define sc(a) scanf("%d", &a) #define _ff(i, a, b) for (int i = (a); i <= (b); ++i) #define lowbit(x) x&(-x) using namespace std; //由於是離線操作,線段樹最多有(N+詢問的個數)棵 const int maxn = 6e4 + 5; const int maxm = 1e4 + 5; const int maxk = maxn * 32; //a陣列為初始陣列,b陣列用來離散化 int a[maxn], b[maxn]; //rt陣列記錄每棵主席樹根節點的下標 int sum[maxk], ls[maxk], rs[maxk], rt[maxn]; int num, n, tot; /* 樹狀陣列ta每個節點記錄的是每棵線段樹的根節點 ul陣列記錄詢問區間下界的節點下移,ur陣列記錄詢問區間上界的節點下移 */ int ta[maxn], ul[maxn], ur[maxn]; struct Query{ int l, r, k; bool flag; }q[maxm]; void build(int &node, int l, int r) { node = ++tot; sum[node] = 0; if (l == r) return; int mid = (l + r) >> 1; build(ls[node], l, mid); build(rs[node], mid + 1, r); } void update(int &node, int pre, int l, int r, int pos, int val) { node = ++tot; sum[node] = sum[pre] + val; ls[node] = ls[pre], rs[node] = rs[pre]; if (l == r) return; int mid = (l + r) >> 1; if (pos <= mid) update(ls[node], ls[pre], l, mid, pos, val); else update(rs[node], rs[pre], mid + 1, r, pos, val); } //傳入的是在初始數組裡的下標 void add(int x, int val) { //記錄其在離散化數組裡的下標 int pos = lower_bound(b + 1, b + 1 + num, a[x]) - b; /* 這裡注意主席樹每棵線段樹的根節點的下標對應的值與初始陣列a下標對應的值是一樣的 因為主席樹的構建是按照初始陣列的順序構造的 */ for (; x <= n; x += lowbit(x)) update(ta[x], ta[x], 1, num, pos, val); } //查詢x位置的字首和,f為真代表查詢上界,f為假查詢下界 int getsum(int x, bool f) { int res = 0; for (; x; x -= lowbit(x)) { if (f) res += sum[ls[ur[x]]]; else res += sum[ls[ul[x]]]; } return res; } /* st代表詢問區間的下界、ed代表詢問區間的上界 stpos代表第st棵線段樹的根節點、edpos代表第ed棵線段樹的根節點 */ int query(int st, int ed, int stpos, int edpos, int l, int r, int pos) { if (l == r) return l; /* 利用字首和求區間和,分別計算權值線段樹(構成主席樹的每棵線段樹都是一棵權值線段樹)和樹狀陣列 只需要計算左區間裡數字的個數,如果pos小於等於左區間的數字的個數,則去查詢左區間,反之查詢右區間, 查詢右區間時,對應的pos要減去左區間數字的個數,因為找的是整個區間的第k個數。 */ int tmp = sum[ls[edpos]] - sum[ls[stpos]] + getsum(ed, true) - getsum(st, false); int mid = (l + r) >> 1; if (pos <= tmp) { //查詢的點在左區間 //樹狀陣列的節點跟隨主席樹的節點下移 for (int i = ed; i; i -= lowbit(i)) ur[i] = ls[ur[i]];//上界下移 for (int i = st; i; i -= lowbit(i)) ul[i] = ls[ul[i]];//下界下移 return query(st, ed, ls[stpos], ls[edpos], l, mid, pos); } else { //查詢的點在右區間 //同上 for (int i = ed; i; i -= lowbit(i)) ur[i] = rs[ur[i]]; for (int i = st; i; i -= lowbit(i)) ul[i] = rs[ul[i]]; return query(st, ed, rs[stpos], rs[edpos], mid + 1, r, pos - tmp); } } signed main() { // freopen("a.in", "r", stdin); char op[5]; int cntb, lbw, Q; int kase; sc(kase); while (kase--) { cntb = 0, tot = 0; sc(n), sc(Q); _ff(i, 1, n) { sc(a[i]); b[++cntb] = a[i]; } //主席樹是離線的,這也是他的侷限所在。 _ff(i, 1, Q) { scanf("%s", op); if (op[0] == 'C') { scanf("%d%d", &q[i].l, &q[i].r); q[i].flag = false; b[++cntb] = q[i].r; } else { scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].k); q[i].flag = true; } } sort(b + 1, b + 1 + cntb); num = unique(b + 1, b + 1 + cntb) - b - 1; build(rt[0], 1, num); _ff(i, 1, n) { lbw = lower_bound(b + 1, b + 1 + num, a[i]) - b; update(rt[i], rt[i - 1], 1, num, lbw, 1); } _ff(i, 1, n) ta[i] = rt[0]; _ff(i, 1, Q) { if (q[i].flag) { //flag為真代表查詢 //查詢並記錄查詢上界和下界的根節點 for (int j = q[i].r; j; j -= lowbit(j)) ur[j] = ta[j]; for (int j = q[i].l - 1; j; j -= lowbit(j)) ul[j] = ta[j]; //查詢返回的是其在離散數組裡的下標 int ans = query(q[i].l - 1, q[i].r, rt[q[i].l - 1], rt[q[i].r], 1, num, q[i].k); printf("%d\n", b[ans]); } else { //把x變成y,相當於刪去一個x,增加一個y,此消彼長 add(q[i].l, -1); a[q[i].l] = q[i].r; add(q[i].l, 1); } } } return 0; }