【题目】
CSP-S 2024 提高级 第一轮(初赛) 完善程序(1)
(序列合并)有两个长度为 N 的单调不降序列 A 和 B,序列的每个元素都是小于
1
0
9
10^9
109 的非负整数。在 A 和 B 中各取一个数相加可以得到
N
2
N^2
N2 个和,求其中第 k 小的和。上述参数满足
N
<
=
1
0
5
N<=10^5
N<=105 和
1
<
=
K
<
=
N
2
1<=K<=N^2
1<=K<=N2
#include <iostream>
using namespace std;
const int maxn = 100005;
int n;
long long k;
int a[maxn], b[maxn];
int *upper_bound(int *a, int *an, int ai) {
int l = 0, r = ___(1)___ ;
while (l < r) {
int mid = (l + r) >> 1;
if (___(2)___) {
r = mid;
} else {
l = mid + 1;
}
}
return ___(3)___ ;
}
long long get_rank(int sum) {
long long rank = 0;
for (int i = 0; i < n; i++) {
rank += upper_bound(b, b + n, sum - a[i]) - b;
}
return rank;
}
int solve() {
int l = 0, r = ___(4)___ ;
while (l < r) {
int mid = ((long long)l + r) >> 1;
if (___(5)___) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; ++i)
cin >> a[i];
for (int i = 0; i < n; ++i)
cin >> b[i];
cout << solve() << endl;
return 0;
}
- (1)处应填( )
A.an-a
B.an-a-1
C.ai
D.ai+1 - (2)处应填( )
A.a[mid]>ai
B.a[mid]>=ai
C.a[mid]<ai
D.a[mid]<=ai - (3)处应填( )
A.a+l
B.a+l+1
C.a+l-1
D.an-l - (4)处应填( )
A.a[n-1]+b[n-1]
B.a[n]+b[n]
C.2*maxn
D.maxn - (5)处应填( )
A.get_rank(mid)<k
B.get_rank(mid)<=k
C.get_rank(mid)>k
D.get_rank(mid)>=k
【题目考点】
1. 二分查找
【解题思路】
int *upper_bound(int *a, int *an, int ai) {
int l = 0, r = ___(1)___ ;
while (l < r) {
int mid = (l + r) >> 1;
if (___(2)___) {
r = mid;
} else {
l = mid + 1;
}
}
return ___(3)___ ;
}
upper_bound的字面意思是上界
在有序序列中,lb、ub是序列的下标。如果左闭右开区间[lb, ub)中的元素都为x,我们说x的下界是lb,上界是ub。如果整个序列是不降序列,那么上界ub位置的值也是大于x的最小值。
根据get_rank函数中的调用语句upper_bound(b, b + n, sum - a[i]) - b
,以及已知b数组的下标范围为[0, n),那么可知upper_bound的意义是:在左闭右开区间[a, an)指向的序列范围内找ai的上界,也就是大于ai的最小值的最小下标,这和STL中upper_bound的参数概念相同。
如果区间[a, an)中第一个元素*a的值就是大于ai的最小值,那么应该返回指针a。
如果ai比区间[a, an)中的元素都大,那么应该返回指向最后一个元素的后一个位置的指针,也就是an。
l、r都是整数,表示a数组的下标,对应的指针分别为a+l, a+r。可能返回的最小指针为a,所以a+l=a, l=0。可能返回的最大指针为an,所以a+r=an,r = an-a。第(1)空选A。
该函数中的二分写法是求满足条件的最小值的写法。
第(2)空填需要满足的条件,二分求的是大于ai的最小值。因此需要满足的条件应该是a[mid]>ai
,第(2)空选A。
二分查找的结果是l或r。
第(3)空返回的是int*类型的指针,应该把结果下标l转为指针a+l
返回。第(3)空选A。
long long get_rank(int sum) {
long long rank = 0;
for (int i = 0; i < n; i++) {
rank += upper_bound(b, b + n, sum - a[i]) - b;
}
return rank;
}
get_rank从字面意义来看,应该是获取加和sum的排名。
设表示排名的变量rank,初值为0。
而后遍历a数组,在a数组中选出元素a[i],而后看一下在b中有多少个元素与a[i]相加的结果都是小于等于sum的。
设j = upper_bound(b, b + n, sum - a[i]) - b
,也就是说b[j]是b数组中第一个大于sum-a[i]的元素。从b[0]~b[j-1]共有j个元素与a[i]相加的和小于等于sum。因此加和sum的排名rank可以增加j。
i从0到n-1循环结束后,统计出共有rank个数对的加和小于等于sum。
因此get_rank求的是小于等于sum的数对的个数。
int solve() {
int l = 0, r = ___(4)___ ;
while (l < r) {
int mid = ((long long)l + r) >> 1;
if (___(5)___) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
看solve函数,是二分查找中求满足条件的最小值的写法
当mid满足条件时,r = mid
当mid不满足条件时,l = mid+1
因此(5)处应该填“不满足条件"。观察第(5)空的选项可知,是get_rank(mid)和k的比较。该题要求的是第k小的和,
在加和小于等于mid的数对的个数为k个的前提下,当mid不断减小直到最小时,mid就是第k小的数对的加和。
当mid较小时不满足条件,当mid较大时满足条件,这样才能求满足条件的最小值。
因此该二分查找的是满足加和<=mid的数对个数>=k的mid的最小值。
也就是满足get_rank(mid)>=k
的mid的最小值
第(5)空填的是“不满足的条件”,是get_rank(mid)>=k
取非,即get_rank(mid)>k
,选A。
第(4)空填结果可能的最大值,最大值应该是a中最大值a[n-1]和b中最大值b[n-1]的加和,因此填a[n-1]+b[n-1]
,选A。
【答案】
- A
- A
- A
- A
- A
注:虽然选项都相同是小概率事件,但要想到5道题出现任何选项排列的概率都是相同的,也就是说出现AAAAA和出现ADBAB等其它任何选项的概率都是相同的。
不要被选项的表象迷惑,从玄学角度出发去修改选项,而是要从题目出发,经过合理的推导,选出能说服自己的答案。