給定 k 和第 n 個排列的「因子進制」表示:
n = S₁·(k-1)! + S₂·(k-2)! + ... + Sₖ₋₁·1! + Sₖ·0!,其中 0 ≤ Sᵢ ≤ k-i
求該排列的具體數列(1~k 的全排列,按字典序)。
Sᵢ 表示第 i 位數字 右側比它小的數的個數。
3 1 4 2:題目直接給出 S₁..Sₖ,要求重構排列。
BIT 大小 k,初始 add(i, 1) 表示數字 i 可用sum(x) 返回 [1, x] 可用數字個數t 小可用數 → 二分 sum(mid) >= t 的最小 midSᵢ,目標順位 t = Sᵢ + 1pos 使 sum(pos) >= tans[i] = pos,從 BIT 中移除 pos (add(pos, -1))ans[1..k]#include <bits/stdc++.h>
using namespace std;
const int MAXK = 50005;
int bit[MAXK];
int k;
int lowbit(int x) { return x & -x; }
void add(int idx, int val) {
for (; idx <= k; idx += lowbit(idx)) bit[idx] += val;
}
int sum(int idx) {
int res = 0;
for (; idx > 0; idx -= lowbit(idx)) res += bit[idx];
return res;
}
// 找第 t 個可用數字 (1-based)
int findKth(int t) {
int l = 1, r = k;
while (l < r) {
int mid = (l + r) >> 1;
if (sum(mid) >= t) r = mid;
else l = mid + 1;
}
return l;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
cin >> k;
fill(bit, bit + k + 1, 0);
for (int i = 1; i <= k; ++i) add(i, 1); // 全可用
vector ans(k + 1);
for (int i = 1; i <= k; ++i) {
int s;
cin >> s;
ans[i] = findKth(s + 1);
add(ans[i], -1); // 標記已用
}
for (int i = 1; i <= k; ++i) {
if (i > 1) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
return 0;
}
利用 BIT 內部結構直接下降查找,無需外層二分:
int findKth(int t) {
int idx = 0;
for (int d = 1 << 15; d; d >>= 1) { // 2^15 > 50000
int next = idx + d;
if (next <= k && bit[next] < t) {
idx = next;
t -= bit[next];
}
}
return idx + 1;
}
總複雜度降為 O(k log k),常數極小。
本題展示了因子進制表示與排列的雙向映射,結合樹狀數組動態維護順序統計,是經典的「離線查詢 + 數據結構」範式。掌握 BIT 的 findKth 優化寫法,能顯著提升競賽編程效率。