跳转至

線段樹套線段樹

常見用途

在算法競賽中,我們有時需要維護多維度信息。在這種時候,我們經常需要樹套樹來記錄信息。

實現原理

我們考慮用樹套樹如何實現在二維平面上進行單點修改,區域查詢。我們考慮外層的線段樹,最底層的 \(1\)\(n\) 個節點的子樹,分別代表第 \(1\) 到第 \(n\) 行的線段樹。那麼這些底層的節點對應的父節點,就代表其兩個子節點的子樹所在的一片區域。

性質

空間複雜度

通常情況下,我們不可能對於外層線段樹的每一個結點都建立一顆子線段樹,空間需求過大。樹套樹一般採取動態開點的策略。單次修改,我們會涉及到外層線段樹的 \(\log{n}\) 個節點,且對於每個節點的子樹涉及 \(\log{n}\) 個節點,所以單次修改產生的空間最多為 \(\log^2{n}\)

時間複雜度

對於詢問操作,我們考慮我們在外層線段樹上進行 \(\log{n}\) 次操作,每次操作會在一個內層線段樹上進行 \(\log{n}\) 次操作,所以時間複雜度為 \(\log^2{n}\)。 修改操作,與詢問操作複雜度相同,也為 \(\log^2{n}\)

經典例題

陌上花開 將第一維排序處理,然後用樹套樹維護第二維和第三維。

示例代碼

第二維查詢

1
2
3
4
5
6
7
8
int tree_query(int k, int l, int r, int x) {
  if (k == 0) return 0;
  if (1 <= l && r <= sec[x].y) return vec_query(ou_root[k], 1, p, 1, sec[x].z);
  int mid = l + r >> 1, res = 0;
  if (1 <= mid) res += tree_query(ou_ch[k][0], l, mid, x);
  if (sec[x].y > mid) res += tree_query(ou_ch[k][1], mid + 1, r, x);
  return res;
}

第二維修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void tree_insert(int &k, int l, int r, int x) {
  if (k == 0) k = ++ou_tot;
  vec_insert(ou_root[k], 1, p, sec[x].z);
  if (l == r) return;
  int mid = l + r >> 1;
  if (sec[x].y <= mid)
    tree_insert(ou_ch[k][0], l, mid, x);
  else
    tree_insert(ou_ch[k][1], mid + 1, r, x);
}

第三維查詢

1
2
3
4
5
6
7
8
int vec_query(int k, int l, int r, int x, int y) {
  if (k == 0) return 0;
  if (x <= l && r <= y) return data[k];
  int mid = l + r >> 1, res = 0;
  if (x <= mid) res += vec_query(ch[k][0], l, mid, x, y);
  if (y > mid) res += vec_query(ch[k][1], mid + 1, r, x, y);
  return res;
}

第三維修改

1
2
3
4
5
6
7
8
void vec_insert(int &k, int l, int r, int loc) {
  if (k == 0) k = ++tot;
  data[k]++;
  if (l == r) return;
  int mid = l + r >> 1;
  if (loc <= mid) vec_insert(ch[k][0], l, mid, loc);
  if (loc > mid) vec_insert(ch[k][1], mid + 1, r, loc);
}

相關算法

面對多維度信息的題目時,如果題目沒有要求強制在線,我們還可以考慮 CDQ 分治,或者 整體二分 等分治算法,來避免使用高級數據結構,減少代碼實現難度。