1. 程式人生 > >權值線段樹(+動態開點)及其簡單應用

權值線段樹(+動態開點)及其簡單應用

它是個啥?

我們都知道,線段樹是一種功能較為強大的資料結構。普通線段樹中我們在每個節點儲存的是某段區間的一些資訊,而權值線段樹,我們存的是值為該下標的數的個數,並通過線段樹統計某段區間的資訊。

它能幹嘛?

最最簡單的用途就是動態統計在某個取值範圍內的數的個數嘛,而這就足夠我們解決許多問題了,例如求前驅、後繼等等。

它有什麼限制?

通常出題人都不會特地去限制權值的範圍,而如果我們非常草率地開出一棵線段樹所有的節點,99.9999.99%都是會MLEMLE的。 那麼我們就需要用一個叫動態開點線段樹的東西來防止這種悲劇發生了。 因為一段區間線上段樹上最多被劃分為O(log2n)O(log_2^n)

個區間,而詢問+修改的數量mm也一定在時間複雜度範圍內,所以動態開點線段樹的時空複雜度都是O(mlog2n)O(mlog_2^n)的,在絕大多數情況下都是可以接收的。(除了某些喪心病狂的出題人出一些臭名昭著的卡log的題)

它怎麼實現?

首先,在座的各位大佬肯定都會線段樹的嘛(^ _ ^) 動態開點的話,我們只需要將每個節點的左右兒子也記錄到該節點的資訊中即可。 每次修改時訪問到未開出的節點時就開出這個點,詢問因為該區間本身就未被更新過,直接返回原始狀態即可,這可以在一定程度上減少時間複雜度。(然而並不影響上界複雜度)

它有什麼代替價值嗎?

權值線段樹可以解決一些平衡樹的問題。當然,如果你想用C

++C++STLSTLsetset大法當然也是可以的啦(然而本蒟蒻並不會用) (P黨勿噴,並且各種建議noip賽後轉c)

它的程式碼實現長咋樣?

本人一直使用的都是感覺較為易理解的普通 (DK) 線段樹,聽說 (事實上) 常數有點大,但出題人如此善良 (嗎?), 應該不會故意卡線段樹的吧 (怎麼不信呢?) 。 這是本人普通平衡樹的程式碼,略醜

#include<cstdio>
using namespace std;
const int L = -1e7 + 5;//儘量多開一點,主要懶得寫區間不存在的特判
const int R = 1e7 + 5;
int rt ,
cnt; struct inm{int l_son , r_son , cnt;}; int read() { char ch = getchar(); bool flag = 1; while(ch < '0' || ch > '9') flag &= ch != '-' , ch = getchar(); int res = 0; while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48) , ch = getchar(); return flag ? res : -res; } struct ST { private: inm tree[3000005]; public: void push_up(int p) { tree[p].cnt = tree[tree[p].l_son].cnt + tree[tree[p].r_son].cnt; } void add(int &p , int l , int r , int x , int k) { if(!p) p = ++cnt; if(l == r) { tree[p].cnt += k; return; } int mid = (l + r) >> 1; if(x <= mid) add(tree[p].l_son , l , mid , x , k); else add(tree[p].r_son , mid + 1 , r , x , k); push_up(p); } int query_cnt(int p , int l , int r , int x , int y)//求l~r區間中數的個數 { if(!p) return 0; if(l == x && r == y) return tree[p].cnt; int mid = (l + r) >> 1; if(y <= mid) return query_cnt(tree[p].l_son , l , mid , x , y); else if(x > mid) return query_cnt(tree[p].r_son , mid + 1 , r , x , y); else return query_cnt(tree[p].l_son , l , mid , x , mid) + query_cnt(tree[p].r_son , mid + 1 , r , mid + 1 , y); } int query_number(int p , int l , int r , int k)//求排名為k的數 { if(l == r) return l; int mid = (l + r) >> 1; if(tree[tree[p].l_son].cnt >= k) return query_number(tree[p].l_son , l , mid , k); else return query_number(tree[p].r_son , mid + 1 , r , k - tree[tree[p].l_son].cnt); } }st; int query_rank(int k)//求k的排名,線上段樹上二分 { return st.query_cnt(rt , L , R , L , k - 1) + 1; } int query_prev(int k)//求k的前驅 { int rank = st.query_cnt(rt , L , R , L , k - 1); return st.query_number(rt , L , R , rank); } int query_next(int k)//求k的後繼 { int rank = st.query_cnt(rt , L , R , L , k) + 1; return st.query_number(rt , L , R , rank); } int main() { int q = read(); while(q--) { int op = read() , k = read(); if(op == 1) st.add(rt , L , R , k , 1); if(op == 2) st.add(rt , L , R , k , -1); if(op == 3) printf("%d\n",query_rank(k)); if(op == 4) printf("%d\n",st.query_number(rt , L , R , k)); if(op == 5) printf("%d\n",query_prev(k)); if(op == 6) printf("%d\n",query_next(k)); } return 0; }