權值線段樹加動態開點
阿新 • • 發佈:2020-07-20
例題
思考
題目給的資料是1e-7到1e7,直接寫線段樹記憶體肯定是比較吃力,而且題目還要維護rank和第k大,這時候就用到動態開點了,因為運算元一共就1e5,所以最多也只需要開\(log_2(2e7)\)大小的陣列。
- 維護size陣列記錄該節點所包含的區間內出現過的數字的節點的個數
- 維護 ls和rs陣列分別記錄左右兒子
動態開點+插入(刪除)節點
- 節點存在代表該節點表示的數存在
- 節點不存在代表該節點表示的數不存在
修改函式
void add(int &rt, int l, int r, int x, int v) { //修改,注意rt是引用,因為下面要進行修改 if (rt == 0) rt = ++nodecnt;//沒有節點,構建該節點 size[rt] += v; // v->+ 加點,v->- 刪點,v取值為{-1,1},-1則該節點的size--,+1則該節點size++ if (l == r) return; //建點到目標位置,結束 int mid = (l + r) >> 1; if (x <= mid) add(ls[rt], l, mid, x, v);//在左邊 else add(rs[rt], mid + 1, r, x, v);//在右邊 }
主函式中
if (op == 1) {
add(root, -1e9, 1e9, x, 1); //新增節點
}
if (op == 2) {
add(root, -1e9, 1e9, x, -1); //刪去節點
}
查詢x的排名
即為x前面存在的節點個數加一
查詢函式
int getnum(int rt, int l, int r, int x) { //求x前有多少數 if (size[rt] == 0 || x > r) { // x>r時,直接返回當前節點下的節點數目 return size[rt]; } int mid = (l + r) >> 1; if (x <= mid) return getnum(ls[rt], l, mid, x); //直接在左邊節點跑就ok return size[ls[rt]] + getnum(rs[rt], mid + 1, r, x); //左側的節點數目加上右側x前的數目 }
主函式中
if (op == 3) {
printf("%d\n", getnum(root, -1e9, 1e9, x) + 1); //求x的排名
}
查詢第k個數
對當前的k值與左區間的size比較
- 小於則k在左側,直接遞迴左兒子
- 大於則k在右側,k減去做兒子的節點數,遞迴右兒子
查詢函式
int getk(int rt, int l, int r, int k) { //求第k大的數 if (l == r) return l; //查詢到k第節點,直接返回 int mid = (l + r) >> 1; if (size[ls[rt]] >= k) return getk(ls[rt], l, mid, k); //左側節點樹大於k,直接跑左側 return getk(rs[rt], mid + 1, r, k - size[ls[rt]]); //左側節點數小於k,減去左側節點數,跑右側 }
主函式
if (op == 4) {
printf("%d\n", getk(root, -1e9, 1e9, x)); //求第x大的數字
}
前趨和後繼
if (op == 5) {
printf("%d\n", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x)));
/*前趨,先求x前有多少個數,假設為num,那麼第num個就是x的前趨*/
}
if (op == 6) {
printf("%d\n", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x + 1) + 1));
/*後繼,求x+1前有多少個數,假設為num,那麼第num+1個數為x的後繼*/
}