2024年6月2日举办的BCSP-X测评已经落下帷幕。我们也第一时间把小学高年级组的基础知识题放入到了我们的OJ系统,有兴趣的老师和同学可以进去自己测试一下。
当我们在录入试题的时候,发现有一道阅读程序单选题好像没有答案。我们尝试分析一下。
试题是这样的:
#include <iostream>
using namespace std;
int n, h[1000005], L, R;
long long m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> h[i];
if (h[i] > R) R = h[i];
}
while (L < R) {
int mid = (L + R + 1) / 2;
long long tmp = 0;
for (int i = 1; i <= n; i++)
if (h[i] > mid)
tmp += h[i] - mid;
if (tmp < m)
R = mid - 1;
else
L = mid;
}
cout << L << endl;
return 0;
}
假设输入的所有数是正整数,其中 n 以及数组元素 h[1], h[2], …, h[n]均不超过 1000000,m 不超过 h[1], h[2], …, h[n]之和,完成下面的判断题和单选题:
(其他题略)
26. (本题 4 分)将第 012 行代码改为( ),程序执行的效果不变。
A. int mid = (L + R) / 2;
B. int mid = (L + R) * 2;
C. int mid = L + R + 1 << 2;
D. int mid = L + R + 1 >> 2;
我们首先来分析这个程序。这个程序是一个二分查找算法的实现,目的是解决一种典型的木材锯切问题,通常被称为“木材采伐”问题。这个问题的核心在于确定一个锯切的高度,使得从一系列不同高度的树木中锯下的木材总长度至少为指定的长度m。
程序的逻辑解释如下:
输入部分:程序首先输入整数 n(树的数量)和长整型 m(需要的木材总长度)。然后输入每棵树的高度。
初始化变量:
- L 和 R 为二分搜索的边界,初始时 L 设为 0(最低可能的锯切高度),R 设为输入的最高树高。
二分查找:
- 通过不断调整 L 和 R 的值,寻找最佳的锯切高度。
- 在每次迭代中,计算中点 mid(当前的尝试锯切高度),并计算所有高于 mid 的树的木材总长度 tmp。
- 如果 tmp 小于所需的木材长度 m,说明 mid 太高,需要降低锯切高度(调整 R)。
- 如果 tmp 大于等于 m,说明 mid 是一个可行的锯切高度,但可能不是最低的,因此尝试增加锯切高度(调整 L)以寻找更低的可能高度。
输出:输出能满足条件的最低锯切高度 L。
26这道题中的四个选项,貌似都无法复合要求。我们逐个分析如下:
A. int mid = (L + R) / 2;
这个选项计算的是 L 和 R 的平均值,通常用于标准的二分查找。这个选项在大多数情况下可能有效,但由于它在计算 mid 时不考虑向上取整,可能在某些情况下(特别是 L 和 R 接近时)与原始方法产生轻微差异,因为原始的 (L + R + 1) / 2 是为了确保 mid 在值偏小时能适当向上取整。int mid = (L + R) * 2;
这个计算的结果会是 L + R 的两倍,显然这并不是正确的方式来找中间值。这将导致 mid 值过大,不符合二分查找的逻辑。int mid = L + R + 1 << 2;
这里 << 2 相当于将数值乘以 4。这实际上是在计算 (L + R + 1) << 2,会导致结果极度偏大,同样不适用。int mid = L + R + 1 >> 2;
这里 >> 2 相当于将数值除以 4。这将会使得计算的 mid 值远远小于正确的中间值,不适合二分查找中计算中值的逻辑。
综上所述,其中选项 A 在多数情况下可能是最接近原始代码的,因为它只是去掉了向上取整的处理。尽管在某些极端的边界条件下可能导致轻微的差异(例如当 L 和 R 相邻时),但在大多数标准的二分查找情况下,它能提供相似的功能。但如果我们使用下面测试数据进行测试:
5
10
10
20
15
10
25
期待输出是17,但如果用A选项去代替第12行的代码,会发生超时。