题解:AtCoder AT_awc0025_e Organizing the Bookshelf
【题目来源】
AtCoder:E - Organizing the Bookshelf
【题目描述】
There are \(N\) books arranged in a horizontal row on Takahashi's bookshelf. Initially, the book at the \(i\)-th position from the left has a durability of \(D_i\).
高桥的书架上水平排列着 \(N\) 本书。初始时,从左数第 \(i\) 个位置上的书的耐久度为 \(D_i\)。
Takahashi performs \(Q\) operations on this bookshelf in order. In the \(j\)-th operation, an integer \(T_j\) is given, and the following procedure is carried out:
高桥按顺序对这个书架执行 \(Q\) 次操作。在第 \(j\) 次操作中,给出一个整数 \(T_j\),并执行以下过程:
- Let \(M\) be the number of books currently remaining on the bookshelf.
设 \(M\) 为当前书架上剩余书籍的数量。 - If \(T_j > M\), the \(T_j\)-th book from the left does not exist, so nothing happens.
如果 \(T_j > M\),则从左数第 \(T_j\) 本书不存在,因此不进行任何操作。 - If \(T_j \leq M\), the durability of the book currently at the \(T_j\)-th position from the left on the bookshelf is decreased by \(1\). If the durability becomes \(0\), that book is immediately removed from the bookshelf. All books that were to the right of the removed book shift one position to the left, so that no gaps remain among the remaining books.
如果 \(T_j \leq M\),则将当前书架上从左数第 \(T_j\) 个位置上的书的耐久度减少 \(1\)。如果耐久度变为 \(0\),该书立即从书架移除。被移除书籍右侧的所有书籍向左移动一个位置,以确保剩余书籍之间没有空隙。
After each operation (including cases where nothing happens), output the number of books remaining on the bookshelf.
每次操作后(包括未发生任何操作的情况),输出书架上剩余的书籍数量。
【输入】
\(N\) \(Q\)
\(D_1\) \(D_2\) \(\ldots\) \(D_N\)
\(T_1\)
\(T_2\)
\(\vdots\)
\(T_Q\)
- The first line contains two space-separated integers: \(N\), the initial number of books, and \(Q\), the number of operations.
- The second line contains \(N\) space-separated integers \(D_1, D_2, \ldots, D_N\), where \(D_i\) represents the durability of the \(i\)-th book from the left.
- Each of the following \(Q\) lines contains a single integer \(T_j\) \((1 \leq j \leq Q)\), representing the target position for the \(j\)-th operation.
【输出】
Output \(Q\) lines. The \(j\)-th line should contain the number of books remaining on the bookshelf after the \(j\)-th operation.
【输入样例】
5 5
1 2 3 1 2
2
4
2
1
3
【输出样例】
5
4
3
2
2
【核心思想】
-
问题分析:给定 \(N\) 本书,每本书有耐久度 \(D_i\)。进行 \(Q\) 次操作,每次操作选择当前书架上从左数第 \(T_j\) 本书,将其耐久度减 \(1\),若耐久度变为 \(0\) 则移除该书。每次操作后输出剩余书籍数量。这是一个树状数组 + 二分查找问题,关键在于动态维护书籍的存在状态并支持第 \(k\) 个存在元素的查询。
-
算法选择:
- 树状数组(Fenwick Tree / BIT):维护每个位置是否存在书(\(1\) 表示存在,\(0\) 表示不存在),支持 \(O(\log N)\) 的单点修改和前缀和查询
- 二分查找(Binary Search):利用树状数组前缀和的单调性,二分查找第 \(k\) 本书的实际位置
- 动态维护:
tn记录当前剩余书籍数量,每次移除书籍时更新
-
关键步骤:
- 初始化:
- 读取 \(N\)(初始书籍数量)、\(Q\)(操作次数)
- 读取每本书的耐久度 \(D[1..N]\)
- 初始化树状数组:对每个位置 \(i\) 执行
add(i, 1),表示该位置有 \(1\) 本书 tn = N,初始剩余书籍数量为 \(N\)
- 处理操作(共 \(Q\) 次):
- 读取目标位置 \(T_j\)
- 无效操作:若 \(T_j > tn\)(目标位置超出当前书籍数量),直接输出
tn - 有效操作:
- 查找实际位置:
pos = find(T_j),通过二分查找找到第 \(T_j\) 本书的实际位置- 二分查找条件:
query(mid) >= T_j,表示前mid个位置中至少有 \(T_j\) 本书
- 二分查找条件:
- 减少耐久度:
d[pos]-- - 移除书籍:若
d[pos] == 0,执行add(pos, -1)将该位置标记为无书,tn-- - 输出
tn
- 查找实际位置:
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(Q \log^2 N)\),每次操作需要二分查找 \(O(\log N)\),每次查找需要 \(O(\log N)\) 的树状数组查询
- 空间复杂度:\(O(N)\),树状数组和耐久度数组
-
树状数组 + 二分查找的核心思想:
- 前缀和表示存在性:树状数组维护前缀和,表示"前 \(x\) 个位置中有多少本书"。这种表示方法天然支持"第 \(k\) 本书在哪里"的查询
- 二分查找定位:利用前缀和的单调性,二分查找最小的位置
pos使得query(pos) >= k。这等价于找到第 \(k\) 个存在书籍的位置 - 动态删除维护:当书籍被移除时,在对应位置执行
add(pos, -1),树状数组自动维护剩余书籍的前缀和信息。后续查询会自动跳过已删除的位置 - 位置映射转换:操作中的 \(T_j\) 是"当前书架上的第 \(T_j\) 本书",需要通过二分查找转换为实际数组位置。这种动态位置映射是问题的核心难点
- 适用于"动态维护序列,查询第 \(k\) 个存在元素"的场景
【解题思路】

【算法标签】
树状数组
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, q, tn; // n: 初始书籍数量,q: 操作次数,tn: 当前剩余书籍数量
int d[N], tr[N]; // d[i]: 第i个位置书的耐久度,tr: 树状数组
// 计算x的二进制表示中最低位的1所代表的值
int lowbit(int x)
{
return x & -x;
}
// 树状数组的更新操作:在位置x增加c
void add(int x, int c)
{
for (int i = x; i <= n; i += lowbit(i))
{
tr[i] += c;
}
}
// 树状数组的查询操作:查询前缀和[1, x]
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += tr[i];
}
return res;
}
// 通过二分查找找到第x本书的实际位置
int find(int x)
{
int l = 1, r = n;
while (l < r)
{
int mid = (l + r) / 2;
if (query(mid) >= x) // 如果前mid个位置中至少有x本书
{
r = mid; // 向左搜索
}
else
{
l = mid + 1; // 向右搜索
}
}
return l; // 返回第x本书的实际位置
}
int main()
{
cin >> n >> q; // 读入初始书籍数量和操作次数
// 初始化
for (int i = 1; i <= n; i++)
{
cin >> d[i]; // 读入每本书的耐久度
add(i, 1); // 树状数组中每个位置初始有1本书
}
tn = n; // 当前剩余书籍数量初始为n
while (q--) // 处理每个操作
{
int t;
cin >> t; // 读入操作目标位置
if (t > tn) // 如果目标位置大于当前剩余书籍数量
{
cout << tn << endl; // 输出剩余书籍数量
continue; // 继续下一个操作
}
// 找到第t本书在当前书架上的实际位置
int pos = find(t);
// 减少该书的耐久度
d[pos]--;
// 如果耐久度降为0,移除该书
if (d[pos] == 0)
{
add(pos, -1); // 树状数组中该位置减少1本书
tn--; // 剩余书籍数量减1
}
cout << tn << endl; // 输出操作后的剩余书籍数量
}
return 0;
}
【运行结果】
5 5
1 2 3 1 2
2
5
4
4
2
3
1
2
3
2
浙公网安备 33010602011771号