1. 程式人生 > 實用技巧 >權值線段樹加動態開點

權值線段樹加動態開點

例題

普通平衡樹

思考

題目給的資料是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的後繼*/
}