题面与数据严重背离,实际评测数据为 POJ 2828 Buy Tickets (插队问题),需使用离线倒序加树状数组二分求解。
OJ: roj
题目 ID: 3165
难度:提高
标签:树状数组二分离线处理
创建: 2026-10-09 11:52
更新: 2026-10-09 11:52
形式化题目
【严重警告:本题题面与实际测试数据完全不符】
原题面描述的是求“最长严格递减子序列的长度及方案数”(类似 USACO 4.3 Buy Low Buy Lower)。然而,经过全量数据的核对与严格实测,本题真实的测试数据是经典问题 POJ 2828 (Buy Tickets)。
为了能通过系统的真实评测,以下给出实际评测数据对应的形式化题意:
有
输入格式:
包含多组测试用例,直到文件结束(EOF)。每组测试用例第一行为
输出格式:
对于每组用例,输出一行
正解
思路
因为后面插入的人会影响前面已排好人员的位置,正向模拟插入会导致
- 最后一个人(第
个人)的位置不会受任何人影响,所以他在最终队伍中的绝对位置必然是 (1-indexed)。 - 倒数第二个人,他的位置在剩下的空位中必定是第
个空位。 - 一般地,倒数第
个人,最终位置必然是当前队伍剩余空位中的第 个。
为了高效查找第
- 初始时,树状数组的
位置都置为 1(表示空位),维护前缀和。 - 寻找第
个空位时,直接在树状数组上利用倍增法进行二分,时间复杂度为 。 - 找到目标空位后将其在树状数组中的值减 1,表示空位被占用,并记录对应人的
。
代码
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-09 11:13
# update_at: 2026-10-09 11:41
import sys
def kth_empty(bit: list[int], k: int, n: int) -> int:
"""倍增求前缀和首次达到 k 的位置,即当前第 k 个空位的下标(1-indexed)。"""
pos = 0
step = 1 << (n.bit_length() - 1)
while step:
nxt = pos + step
if nxt <= n and bit[nxt] < k:
k -= bit[nxt]
pos = nxt
step >>= 1
return pos + 1
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
out: list[str] = []
for n in data:
try:
op = [(next(data), next(data)) for _ in range(n)] # 每行 (P_i, V_i)
except StopIteration: # 输入被截断,放弃这一组
break
bit = [0] + [i & -i for i in range(1, n + 1)] # 初始全空:bit[i] = i & -i
ans = [0] * (n + 1)
# 倒序:第 i 个人必定落在当前剩余空位的第 P_i + 1 个
for pos, val in reversed(op):
p = kth_empty(bit, pos + 1, n)
ans[p] = val
i = p
while i <= n: # 占掉这个空位
bit[i] -= 1
i += i & -i
out.append(''.join(f'{x} ' for x in ans[1:])) # 行末也带一个空格
sys.stdout.write('\n'.join(out) + '\n' if out else '')
if __name__ == '__main__':
solve()/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-09 11:13
* update_at: 2026-10-09 11:41
*/
/* ROJ 3165 「Buy Low Buy Lower」低买 */
/* ⚠ 题面与真实数据不符:题面是「最长严格递减子序列 + 方案数」,真实数据是
* POJ 2828 Buy Tickets 插队问题。这里按真实数据实现。
*
* 插队问题:N 个人依次入队,第 i 个人插到当时队伍的第 P_i 个位置(0-indexed),
* 求最终队伍里每个人的 V_i。
*
* 做法:正向模拟插入的复杂度是 O(N^2),改用「离线倒序 + 树状数组上二分」。
* 最后一个插入的人位置不再被后来者影响,他在最终队伍中的位置就是第 P_N+1 个空位;
* 倒着往前,每人都占据当前剩余空位中的第 P_i+1 个。树状数组维护「每个位置是否为空」
* 的前缀和,用倍增在 O(log N) 内定位第 k 个空位。总复杂度 O(N log N)。 */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 200005; // N ≤ 200000,位置按下标 1..N 使用
struct Op {
int pos; // P_i:插入到当时队伍的第 pos 个位置(0-indexed)
ll val; // V_i:这个人的数值
};
int n; // 当前组的操作条数
Op op[MAXN]; // op[i]:第 i 条插入指令(按下标 0..n-1 读入,倒序处理)
ll ans[MAXN]; // ans[p]:最终队伍第 p 个位置(1-indexed)上的人的数值
int bit[MAXN]; // 树状数组,存「第 p 个位置是否为空」的前缀和;下标 ≤ 200000,用 int
// 单点加减:位置 i 的空位数变化 delta(0 表示已被占用)
void bit_add(int i, int delta) {
for (; i <= n; i += i & -i) {
bit[i] += delta;
}
}
// 倍增求前缀和首次达到 k 的位置,即当前第 k 个空位的下标(1-indexed)
int bit_kth(int k) {
int pos = 0;
int sum = 0;
int step = 1;
while ((step << 1) <= n) {
step <<= 1;
}
for (; step > 0; step >>= 1) {
int next_pos = pos + step;
if (next_pos <= n && sum + bit[next_pos] < k) {
pos = next_pos;
sum += bit[next_pos];
}
}
return pos + 1;
}
void solve() {
for (int i = 0; i < n; i++) {
cin >> op[i].pos >> op[i].val;
}
// 初始时每个位置都空着:bit[i] = i & -i 是「前 i 个位置全为 1」的树状数组初值
for (int i = 1; i <= n; i++) {
bit[i] = i & -i;
}
// 倒序处理:第 i 个人必定落在当前剩余空位的第 op[i].pos + 1 个
for (int i = n - 1; i >= 0; i--) {
int p = bit_kth(op[i].pos + 1);
ans[p] = op[i].val;
bit_add(p, -1); // 这个空位被占掉
}
// 输出最终队伍;每个数字后都带一个空格,行末也保留(与数据格式一致)
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
while (cin >> n) {
solve();
}
return 0;
}复杂度
- 时间复杂度:建树
,对于 个人,每次倍增查询第 个空位耗时 。总时间复杂度为 。 - 空间复杂度:需要若干大小为
的数组,空间复杂度为 。
总结
-
★★★ 题面 ≠ 数据(实锤)
- 输出是输入
的排列(12/12组成立)。 - 输出严格递减(12/12组全假,存在不降反升的反例如
26816 <= 30894)。 .in是(P,V)对,且解析后无剩余 token。- 多用例直到 EOF,且
违反 0 次(最大测到 199121)。 - (i) 按题面解析有大量未消费 token:如点 1 题面读
后实际剩 23 个;点 10 题面读 剩 200000 个,题面格式不成立。 - (ii) 算法对照全错:按照题面所写的 LDS 算法跑题面样例完全正确,但在真实数据上 10/10 全错,输出少得可怜的长度和方案,而期望却是每行
个整数。这排除了“看错题”可能,实属换题。 - (iii) 反向验证崩溃:题面算法跑真实数据,C++ 版报
rc=-10 (SIGBUS),因为题面格式将价格误当作下一组的从而迅速越界崩溃。
- 输出是输入
-
★ 实际考点:POJ 2828 (Buy Tickets) 插队问题,离线倒序 + 树状数组求第
空位, 。 -
★★★
V7算法指纹: 操作计数证明实现确为:在点 10 处 kth_calls = 200000,kth_steps = 3,600,000,kth_steps / N = 18 = ⌈log₂ 200000⌉精确吻合。库函数替换或不可能产生这个指纹,这是本算法复杂度的强机械证据。 -
★★
V8辨识度:点粒度无分辨力(一旦错全是 10/10 错),token 粒度有分辨力。插队语义被数据唯一确定(6 个候选解释只有“倒序+第个空位”能通过,不同候选的首个分歧 token 列各不相同)。 -
★★ 多解性(独立验证): 通过全排列枚举
的所有情况,200组随机数据下,满足插队过程的解集合大小恒为 1。另外 4 种独立实现(BIT二分 / 正序 / 空位链表)结果 MD5 完全一致,确认唯一确定,不需要 spj。 -
★ 逐点表: | 点 | 组数 | N | P 范围 | V 范围 | cpp ms | cpp MB | py ms | py MB | |—|—|—|—|—|—|—|—|—| | 1 | 3 | 5,4,4 | [0,4] | [33,31492] | 8.2 | 8.4 | 17.6 | 8.4 | | 2 | 1 | 5 | [0,3] | [30535,32766] | 8.7 | 8.4 | 17.0 | 8.3 | | 3 | 1 | 10 | [0,7] | [20409,32761] | 8.8 | 8.4 | 17.1 | 8.4 | | 4 | 1 | 15 | [0,9] | [5338,32766] | 8.8 | 8.4 | 18.0 | 8.3 | | 5 | 1 | 50 | [0,48] | [15089,32766] | 9.1 | 8.4 | 17.2 | 8.5 | | 6 | 1 | 100 | [0,88] | [10641,32765] | 8.6 | 8.4 | 17.2 | 8.4 | | 7 | 1 | 1000 | [0,982] | [14607,32766] | 8.9 | 8.4 | 18.3 | 8.8 | | 8 | 1 | 5000 | [0,4949] | [5633,32766] | 9.5 | 8.4 | 27.2 | 10.4 | | 9 | 1 | 10000 | [0,9886] | [16740,32764] | 10.7 | 8.4 | 41.6 | 11.8 | | 10 | 1 | 200000 | [0,199121] | [0,32767] | 53.4 | 8.4 | 619.5 | 78.6 |
-
★ 时间与空间:C++ 峰值 53 ms / 8.4 MB,限制 1000 ms / 128 MB(余量 19×);Python 峰值 619 ms / 78.6 MB,余量约 1.6×。虽然 Python 接近上限,但在本机 Python 3.14 环境下复测为 358 ms,较为宽松,能稳定通过。
-
★ 输出格式:
- 行末确实有多余空格(12/12行的最后一个数字后有空格再换行)。
- 行尾是 LF,不是 CRLF。
- 行数恰好等于组数(点1有3行对应3组,其余1行对应1组)。
.in数据点1末尾无换行,其余存在换行。
-
★
V1反事实表: | 变体 | 改动 | 失败点数 | |—|—|—| |va| 倒序 → 正序 | 10/10 | |vb| 第 P 个空位 → 第 P 个绝对位置(不跳空位)| 10/10 | |vc| P 当 1-indexed(k = max(P,1))| 10/10 | |vc2| off-by-one(k = P+2)| 10/10 | |vd| 去掉行尾空格 | 10/10(字节级)| |vg| 行尾 LF → CRLF | 10/10(字节级)| |vf| BIT → 朴素正序插队 | 0/10(如预期,仅影响速度)| | ve2| BIT → 空位链表倒序 | 0/10(如预期)| -
★
V2/V4:V2字面量:无硬编码(main.cpp的200005仅为宏;输出含 32716 个独立值,与源码的 0/1 交集纯属巧合)。V4结构不变量:.in完整解析为多组(N, N×(P,V))毫无多余 token;.out行数=组数,每行 token=该组 N,每行都是该组 V 的排列。
-
★
T9五项:- 类型转换:无复杂类型转换,全在整数范畴操作。
- 溢出风险:无,所有数值和前缀和在
的规模下,都在 32 位带符号整型 int范围内。 - 整除取模:无。
- 浮点格式化:无。
- 排序字典序:不需要任何字典序与排序干预。