HDU 5412 要求在一個數列上支持單點修改與區間第 K 小查詢。由於數值範圍較大,且操作包含修改,傳統的靜態主席樹難以直接應用。本題採用線段樹套笛卡爾樹的動態結構:先對所有出現過的數值進行離散化,建立一棵值域線段樹,每個線段樹節點維護一棵笛卡爾樹,用於記錄該值域區間內元素的下標集合。笛卡爾樹兼具二叉搜索樹與堆的性質,能以期望 O(log n) 時間完成插入、刪除與排名查詢,從而高效解決動態區間第 K 小問題。
root[u]。key(下標)、val(隨機優先級)、cnt(重複計數)、siz(子樹大小)、左右孩子指標。// 旋轉操作,維持堆性質
void rotate(int &u, int d) {
int k = nd[u].ch[d];
nd[u].ch[d] = nd[k].ch[d^1];
nd[k].ch[d^1] = u;
maintain(u); maintain(k);
u = k;
}
// 插入下標 key
void insert(int &u, int key) {
if (u == 0) u = newNode(key);
else if (nd[u].key == key) nd[u].cnt++;
else {
int d = nd[u].key < key;
insert(nd[u].ch[d], key);
if (nd[u].val < nd[nd[u].ch[d]].val) rotate(u, d);
}
maintain(u);
}
// 刪除下標 key
void erase(int &u, int key) {
if (nd[u].key == key) {
if (nd[u].cnt == 1) {
if (nd[u].ch[0] == 0 && nd[u].ch[1] == 0) { u = 0; return; }
rotate(u, nd[nd[u].ch[0]].val < nd[nd[u].ch[1]].val);
erase(u, key);
} else nd[u].cnt--;
} else erase(nd[u].ch[nd[u].key < key], key);
maintain(u);
}
// 查詢笛卡爾樹中 key <= p 的元素個數
int count(int u, int p) {
if (u == 0) return 0;
if (nd[u].key > p) return count(nd[u].ch[0], p);
return nd[u].cnt + nd[nd[u].ch[0]].siz + count(nd[u].ch[1], p);
}
// 線段樹單點更新:在值域位置 p 插入/刪除下標 x
void modify(int u, int l, int r, int p, int x, int v) {
if (v > 0) insert(root[u], x);
else erase(root[u], x);
if (l == r) return;
int mid = (l + r) >> 1;
if (p <= mid) modify(lson(u), l, mid, p, x, v);
else modify(rson(u), mid+1, r, p, x, v);
}
// 線段樹上二分查詢區間 [x, y] 第 k 小
int query(int u, int l, int r, int x, int y, int k) {
if (l == r) return l;
int mid = (l + r) >> 1;
int siz = count(root[lson(u)], y) - count(root[lson(u)], x);
if (siz >= k) return query(lson(u), l, mid, x, y, k);
else return query(rson(u), mid+1, r, x, y, k - siz);
}
find(x)。find(A[i]) 及其下標 i 插入線段樹。count 統計左子樹滿足條件的元素個數,決定遞歸方向,最終得到離散化值,映射回原始值輸出。線段樹套笛卡爾樹是解決動態區間第 K 小問題的經典方案之一。笛卡爾樹隨機化特性使得代碼實現相對簡潔,且期望效能優異。本題通過離散化將數值域壓縮,結合線段樹分治值域、笛卡爾樹維護下標集合,完美應對了單點修改與區間查詢的混合操作。