题解:AtCoder AT_awc0020_e Shelving Books on a Bookshelf
【题目来源】
AtCoder:E - Shelving Books on a Bookshelf
【题目描述】
Takahashi, working as a librarian, has been assigned the task of shelving \(N\) books onto a bookshelf.
高桥作为一名图书管理员,被分配了一项任务,要将 \(N\) 本书上架到一个书架上。
The bookshelf has \(M\) empty spaces arranged in a row, numbered from left to right as space \(1\), space \(2\), \(\ldots\), space \(M\). Each space \(j\) has an integer value \(C_j\) representing its "weight capacity," and each space can hold at most \(1\) book.
书架上有 \(M\) 个空位排成一行,从左到右编号为位置 \(1\),位置 \(2\), \(\ldots\),位置 \(M\)。每个位置 \(j\) 有一个整数值 \(C_j\) 表示其"承重能力",每个位置最多能放 \(1\) 本书。
The books are numbered from \(1\) to \(N\), and book \(i\) has an integer value \(W_i\) representing its "weight."
书籍编号从 \(1\) 到 \(N\),书 \(i\) 有一个整数值 \(W_i\) 表示其"重量"。
Takahashi determines the placement for each book one at a time, in the order book \(1\), book \(2\), \(\ldots\), book \(N\), using the following procedure:
高桥按照书籍 \(1\),书籍 \(2\), \(\ldots\),书籍 \(N\) 的顺序,使用以下步骤逐一确定每本书的放置位置:
- Among the spaces that do not yet have a book placed on them, consider as candidates those whose weight capacity is at least the weight of the book (i.e., \(C_j \geq W_i\)).
在尚未放置书籍的位置中,考虑那些承重能力不低于书籍重量的位置(即 \(C_j \geq W_i\))作为候选位置。 - If there is at least one candidate, place the book on the candidate space with the smallest number. The space where a book is placed becomes occupied and can no longer be selected for subsequent books.
如果至少有一个候选位置,则将这本书放在编号最小的候选位置上。被放置书籍的位置变为已占用,不能再为后续书籍所选。 - If there are no candidates, give up on shelving that book. A book that is given up on is not placed on the bookshelf and is never reconsidered.
如果没有候选位置,则放弃上架这本书。被放弃的书籍不会被放置在书架上,且后续也不再被考虑。
After considering the placement for all books, determine the number of books that were successfully placed on the bookshelf.
在考虑了所有书籍的放置后,确定成功放置在书架上的书籍数量。
【输入】
\(N\) \(M\)
\(W_1\) \(W_2\) \(\ldots\) \(W_N\)
\(C_1\) \(C_2\) \(\ldots\) \(C_M\)
- The first line contains \(N\), the number of books, and \(M\), the number of spaces on the bookshelf, separated by a space.
- The second line contains the weights of each book \(W_1, W_2, \ldots, W_N\), separated by spaces.
- The third line contains the weight capacities of each space \(C_1, C_2, \ldots, C_M\), separated by spaces.
【输出】
Print the number of books that were successfully placed on the bookshelf, on a single line.
【输入样例】
4 5
3 5 2 7
4 6 3 8 2
【输出样例】
4
【核心思想】
-
问题分析:给定 \(N\) 本书(重量 \(W_i\))和 \(M\) 个书架位置(承重能力 \(C_j\)),按顺序放置每本书,每本书选择编号最小且承重能力 \(\geq\) 书重量的位置。求最多能放置多少本书。这是一个线段树 + 二分查找问题,关键在于快速找到满足条件的第一个位置。
-
算法选择:
- 线段树(Segment Tree):维护每个区间的最大承重能力,支持 \(O(\log M)\) 查询和更新
- 区间最大值剪枝:
find函数中,若当前区间最大值 \(< W_i\),直接返回 \(-1\)(不可能找到) - 在线二分查找:利用线段树结构,优先在左子树查找,保证找到的是编号最小的满足条件的位置
- 单点更新:找到位置后,将其值设为 \(-\infty\)(表示已占用)
-
关键步骤:
- 初始化:
- 读取 \(N\)(书的数量)、\(M\)(位置数量)
- 读取 \(W[1..N]\)(每本书的重量)、\(C[1..M]\)(每个位置的承重能力)
- 构建线段树,初始值为 \(C[j]\)
- 处理每本书(遍历 \(i\) 从 \(1\) 到 \(N\)):
- 查找位置:
pos = find(1, 1, M, W[i]),在线段树中查找第一个容量 \(\geq W[i]\) 的位置- 若当前区间最大值 \(< W[i]\),返回 \(-1\)
- 优先在左子树查找,保证编号最小
- 左子树找不到则在右子树查找
- 放置书籍:若
pos != -1cnt++(成功放置的书数加 \(1\))update(1, pos, -INF)(将该位置标记为已使用)
- 查找位置:
- 输出答案:
cnt
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O((N + M) \log M)\),建树 \(O(M \log M)\),每本书的查找和更新 \(O(\log M)\)
- 空间复杂度:\(O(M)\),线段树需要 \(4M\) 个节点空间
-
线段树二分查找的核心思想:
- 区间最值剪枝:利用线段树维护的区间最大值,快速排除不可能包含答案的子树
- 优先左子树:保证找到的是编号最小的满足条件的位置(贪心策略)
- 单点标记:将已使用位置设为 \(-\infty\),既标记占用又不影响其他查询
- 在线处理:按顺序处理每本书,实时更新书架状态
- 适用于任务调度、资源分配、贪心匹配类问题
【解题思路】

【算法标签】
线段树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, INF = 1e9;
int n, m, cnt; // n: 任务数量,m: 机器数量,cnt: 可完成的任务数
int w[N], c[N]; // w[i]: 第i个任务所需容量,c[i]: 第i台机器的容量
struct Node
{
int l, r; // 区间左右端点
int dt, mx; // dt: 懒标记,mx: 区间最大值
}tr[N * 4]; // 线段树数组,开4倍空间
// 从子节点更新父节点的信息
void pushup(int u)
{
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx); // 取左右子树的最大值
}
// 下传懒标记
void pushdown(int u)
{
auto &root = tr[u], &l = tr[u << 1], &r = tr[u << 1 | 1];
l.dt += root.dt, l.mx += root.dt; // 更新左子树
r.dt += root.dt, r.mx += root.dt; // 更新右子树
root.dt = 0; // 清空当前节点的懒标记
}
// 构建线段树
void build(int u, int l, int r)
{
if (l == r) // 叶节点
{
tr[u] = {l, r, 0, c[l]}; // 初始懒标记为0,值为机器容量
}
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 递归构建左右子树
pushup(u); // 更新当前节点的值
}
}
// 单点更新:将位置pos的值更新为d
void update(int u, int pos, int d)
{
if (tr[u].l == tr[u].r) // 找到叶节点
{
tr[u].mx = d; // 更新值
return;
}
else
{
pushdown(u); // 下传懒标记
int mid = tr[u].l + tr[u].r >> 1;
if (pos <= mid) // 在左子树
{
update(u << 1, pos, d);
}
if (pos > mid) // 在右子树
{
update(u << 1 | 1, pos, d);
}
pushup(u); // 更新当前节点的值
}
}
// 区间查询:查询区间[l, r]的最大值
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) // 当前节点完全包含在查询区间内
{
return tr[u].mx;
}
else
{
pushdown(u); // 下传懒标记
int mid = tr[u].l + tr[u].r >> 1;
int res = -INF; // 初始化为负无穷
if (l <= mid) // 与左子树有交集
{
res = query(u << 1, l, r);
}
if (r > mid) // 与右子树有交集
{
res = max(res, query(u << 1 | 1, l, r)); // 取最大值
}
return res;
}
}
// 在区间[l, r]中查找第一个大于等于val的位置,找不到返回-1
int find(int u, int l, int r, int val)
{
if (tr[u].mx < val) // 当前区间最大值小于val,不可能找到
{
return -1;
}
if (tr[u].l == tr[u].r) // 叶节点
{
return tr[u].l; // 返回位置
}
int mid = (tr[u].l + tr[u].r) >> 1;
int res = -1;
if (l <= mid) // 先在左子树找
{
res = find(u << 1, l, r, val);
}
if (res == -1 && r > mid) // 左子树没找到,在右子树找
{
res = find(u << 1 | 1, l, r, val);
}
return res;
}
int main()
{
cin >> n >> m; // 读入任务数量和机器数量
for (int i = 1; i <= n; i++)
{
cin >> w[i]; // 读入每个任务所需容量
}
for (int i = 1; i <= m; i++)
{
cin >> c[i]; // 读入每台机器的容量
}
build(1, 1, m); // 构建线段树,管理机器容量
for (int i = 1; i <= n; i++) // 遍历每个任务
{
// 查找第一个容量大于等于w[i]的机器
int pos = find(1, 1, m, w[i]);
if (pos != -1) // 找到了合适的机器
{
cnt++; // 可完成的任务数加1
update(1, pos, -INF); // 将该机器的容量设为负无穷,表示已使用
}
}
cout << cnt << endl; // 输出可完成的任务数
return 0;
}
【运行结果】
4 5
3 5 2 7
4 6 3 8 2
4
浙公网安备 33010602011771号