給定長度 n ≤ 100,000 的互不相同整數數組,回答 m ≤ 5,000 個查詢:區間 [l, r] 中第 k 小的數是多少?
[1..m](m ≤ n)。n+1 棵線段樹版本:root[i] 表示前綴 a[1..i] 的頻率統計樹。root[i] 由 root[i-1] 經過 O(log n) 節點新建/復用得到(可持久化)。[l, r] 第 k 小:用 root[r] 減去 root[l-1],在差值樹上二分尋找第 k 個 1。const int MAXN = 100000;
const int MAXM = MAXN * 20; // 每次插入新建 logN 個節點,20 足夠
int lc[MAXM], rc[MAXM], sum[MAXM]; // 左右孩子、區間和
int roots[MAXN + 1]; // 版本根節點
int tot = 0; // 節點池指針
int sorted[MAXN + 1]; // 離散化後有序數組
build(1, m)int build(int L, int R) {
int id = ++tot;
sum[id] = 0;
if (L < R) {
int mid = (L + R) >> 1;
lc[id] = build(L, mid);
rc[id] = build(mid+1, R);
}
return id;
}
update(pre, L, R, pos, val)int update(int pre, int L, int R, int pos, int val) {
int cur = ++tot;
lc[cur] = lc[pre];
rc[cur] = rc[pre];
sum[cur] = sum[pre] + val;
if (L < R) {
int mid = (L + R) >> 1;
if (pos <= mid)
lc[cur] = update(lc[pre], L, mid, pos, val);
else
rc[cur] = update(rc[pre], mid+1, R, pos, val);
}
return cur;
}
只在訪問路徑上新建節點(log n 個),其餘指向舊版本,實現共享。
query(u, v, L, R, k)int query(int u, int v, int L, int R, int k) {
if (L == R) return L;
int mid = (L + R) >> 1;
int leftSum = sum[lc[v]] - sum[lc[u]]; // [l,r] 左半部計數
if (k <= leftSum)
return query(lc[u], lc[v], L, mid, k);
else
return query(rc[u], rc[v], mid+1, R, k - leftSum);
}
u = root[l-1], v = root[r]。左子樹計數差即為區間左半部元素個數。
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
while (cin >> n >> q) {
tot = 0;
vector<int> a(n+1), vals(n+1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
vals[i] = a[i];
}
// 離散化
sort(vals.begin()+1, vals.begin()+n+1);
int m = unique(vals.begin()+1, vals.begin()+n+1) - vals.begin() - 1;
// 建空樹版本 0
roots[n+1] = build(1, m);
// 從右向左構建前綴版本(或從左向右,查詢時調整 u/v)
for (int i = n; i >= 1; --i) {
int pos = lower_bound(vals.begin()+1, vals.begin()+m+1, a[i]) - vals.begin();
roots[i] = update(roots[i+1], 1, m, pos, 1);
}
while (q--) {
int l, r, k;
cin >> l >> r >> k;
int idx = query(roots[l], roots[r+1], 1, m, k);
cout << vals[idx] << '\n';
}
}
return 0;
}
| 操作 | 時間 | 空間 |
|---|---|---|
| 建空樹 | O(m) | O(m) |
| n 次更新 | O(n log m) | O(n log m) 節點 |
| 單次查詢 | O(log m) | O(1) |
| 總計 | O((n+q) log n) | O(n log n) ≈ 200 萬節點 |
unique 返回尾後迭代器,長度計算別漏 -1。root[i] 表示 [i..n] 後綴,查詢用 root[l] 和 root[r+1]。若構建前綴 [1..i],查詢用 root[l-1] 和 root[r]。統一約定即可。MAXN * 20 針對 n=1e5, log2(1e5)≈17,20 留有餘量。scanf/printf 或 ios::sync_with_stdio(false); cin.tie(nullptr);。| 方法 | 預處理 | 查詢 | 空間 | 適用場景 |
|---|---|---|---|---|
| 主席樹 | O(n log n) | O(log n) | O(n log n) | 靜態區間第 k 小,在線查詢 |
| 劃分樹 | O(n log n) | O(log n) | O(n log n) | 同主席樹,常數稍大 |
| 莫隊 + BIT | O(1) | O(√n log n) | O(n) | 離線查詢,可修改版本 |
| Treap/Splay | O(n log n) | O(log n) | O(n) | 動態插入刪除 |