首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

POJ 2104 K-th Number:劃分樹(主席樹前身)解決區間第 k 小問題

     編輯:關於C++

問題描述

給定長度 n (≤100,000) 的互不相同整數數組,回答 m (≤5,000) 個查詢:區間 [i, j] 中第 k 小的數是多少?

示例:a = [1,5,2,6,3,7,4],查詢 Q(2,5,3) → 區間 [5,2,6,3] 排序後 [2,3,5,6],第 3 小為 5。

為何不用平衡樹 + 排序?

POJ 2761 區間不包含關係可用「排序值 + 平衡樹維護位置」。但本題區間任意重疊,存在包含關係,無法離線排序處理,需在線數據結構。

劃分樹核心思想

類似線段樹,但每層將區間按中位數劃分:左子樹存 ≤ 中位數的元素,**保持相對順序**;右子樹存 > 中位數的元素。構建過程記錄每個位置進入左子樹的元素個數 num[level][pos]。

構建過程

// val[0][i] = 原始數組
// 排序副本 sorted[1..n] 用於確定中位數
void build(int l, int r, int level) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    int mid_val = sorted[mid];
    int same = mid - l + 1; // 左子樹應分配個數
    int lpos = l, rpos = mid + 1;
    
    for (int i = l; i <= r; ++i) {
        if (val[level][i] < mid_val) {
            val[level+1][lpos++] = val[level][i];
            num[level][i] = num[level][i-1] + 1;
        } else if (val[level][i] > mid_val) {
            val[level+1][rpos++] = val[level][i];
            num[level][i] = num[level][i-1];
        } else { // 等於中位數,優先填左子樹直到滿額
            if (same > 0) {
                val[level+1][lpos++] = val[level][i];
                num[level][i] = num[level][i-1] + 1;
                --same;
            } else {
                val[level+1][rpos++] = val[level][i];
                num[level][i] = num[level][i-1];
            }
        }
    }
    build(l, mid, level+1);
    build(mid+1, r, level+1);
}

查詢過程

查詢 [L, R] 第 k 小:

int query(int L, int R, int k, int l, int r, int level) {
    if (l == r) return val[level][l];
    int mid = (l + r) >> 1;
    int in_left = num[level][R] - num[level][L-1]; // 區間內落入左子樹的個數
    
    if (in_left >= k) {
        // 答案在左子樹,新區間為左子樹中對應位置
        int newL = l + num[level][L-1];
        int newR = newL + in_left - 1;
        return query(newL, newR, k, l, mid, level+1);
    } else {
        // 答案在右子樹,第 k-in_left 小
        int newL = mid + 1 + (L - l - num[level][L-1]);
        int newR = newL + (R - L + 1 - in_left) - 1;
        return query(newL, newR, k - in_left, mid+1, r, level+1);
    }
}

關鍵點總結

概念說明
val[level][i]第 level 層第 i 位置的值
num[level][i]第 level 層前 i 個位置中,進入左子樹的個數(前綴和)
中位數選擇用排序後數組的中位數,保證左右子樹大小平衡
等於中位數處理優先分配給左子樹,直到左子樹達到應有大小
查詢映射通過 num 前綴和將原區間映射到子樹區間

複雜度

  • 構建:O(n log n)
  • 單次查詢:O(log n)
  • 總時間:O((n+m) log n) ≪ 20s 時限
  • 空間:O(n log n) ≈ 100,000 × 17 ≈ 1.7M int

現代替代:主席樹(可持久化線段樹)

劃分樹是主席樹的前身。主席樹每版本對應前綴,查詢直接用兩版本相減,代碼更簡潔、常數更小。但劃分樹無需動態分配節點,數組實現極快,仍是競賽常用選擇。

總結

本題是「區間第 k 小」的經典在線問題。劃分樹通過「按值域劃分 + 記錄位置映射」將問題轉化為在子樹中遞歸查詢。掌握構建時的等值元素分配邏輯、查詢時的區間映射公式是核心。面試中可對比劃分樹、主席樹、線段樹套平衡樹、莫隊算法等多種解法優劣。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved