給定 n 個數,按順序插入序列。第 i 個數插入位置為 pos[i] (0-based,在當前序列中)。求最終序列。
示例:n=4,(pos, val) = (0,77), (1,51), (1,33), (2,69) → 最終 77 33 69 51。
正序插入需移動後續元素,困難。逆序思考:最終序列長度固定為 n。最後插入的元素位置已定,倒數第二個插入時,它的位置在「剩餘空位」中確定……
n 個位置,初始值 1 (空位)空位總數i = n 到 1 逆序處理:
pos[i]+1 個空位 (1-based)val[i] 放入該位置struct Node { int cnt; } tree[4*MAXN];
void build(int p, int l, int r) {
tree[p].cnt = r - l + 1;
if (l == r) return;
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
}
// 找第 k 個空位 (k 1-based),並佔用
int query(int p, int l, int r, int k) {
tree[p].cnt--;
if (l == r) return l;
int mid = (l+r)/2;
if (k <= tree[p*2].cnt) return query(p*2, l, mid, k);
else return query(p*2+1, mid+1, r, k - tree[p*2].cnt);
}
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200010;
struct Node { int cnt; } tree[4*MAXN];
int pos[MAXN], val[MAXN], ans[MAXN];
void build(int p, int l, int r) {
tree[p].cnt = r - l + 1;
if (l == r) return;
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
}
int query(int p, int l, int r, int k) {
tree[p].cnt--;
if (l == r) return l;
int mid = (l+r)/2;
if (k <= tree[p*2].cnt) return query(p*2, l, mid, k);
else return query(p*2+1, mid+1, r, k - tree[p*2].cnt);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
while (cin >> n) {
for (int i = 1; i <= n; ++i) {
cin >> pos[i] >> val[i];
++pos[i]; // 0-based → 1-based (第 k 個空位)
}
build(1, 1, n);
for (int i = n; i >= 1; --i) {
int idx = query(1, 1, n, pos[i]);
ans[idx] = val[i];
}
for (int i = 1; i <= n; ++i) {
if (i > 1) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
return 0;
}
本題是線段樹離線處理動態序列的教科書級範例:逆序思維 + 空位計數 + 第 k 個查找,完美解決「動態插入查詢最終序列」問題。理解此模式,可舉一反三解決大批「動態序列構造」題目。