CSP-S2022 初赛 完善程序-1

题面

已知两个长度均为 \(n\) 的有序数组 \(a_1\)\(a_2\)(均为递增序,但不保证严格单调递增),并且给定正整数 \(k (1 \leq k \leq 2n)\),求数组 \(a_1\)\(a_2\) 归并排序后的数组里第 \(k\) 小的数值。

试补全程序。

#include <bits/stdc++.h>
using namespace std;

int solve(int *a1, int *a2, int n, int k) {
    int left1 = 0, right1 = n - 1;
    int left2 = 0, right2 = n - 1;
    while (left1 <= right1 && left2 <= right2) {
        int m1 = (left1 + right1) >> 1;
        int m2 = (left2 + right2) >> 1;
        int cnt = ①;
        if (②) {
            if (cnt < k) left1 = m1 + 1;
            else right2 = m2 - 1;
        } else {
            if (cnt < k) left2 = m2 + 1;
            else right1 = m1 - 1;
        }
    }
    if (③) {
        if (left1 == 0) {
            return a2[k - 1];
        } else {
            int x = a1[left1 - 1], ④;
            return std::max(x, y);
        } 
    } else {
            if (left2 == 0) {
                return a1[k - 1];
            } else {
                int x = a2[left2 - 1], ⑤;
                return std:: max(x, y);
            }
    }
}

① ~ ⑤ 处应填( )

题号 A B C D
1. A. (m1 + m2) * 2 B. (m1 - 1) + (m2 - 1) C. m1 + m2 D. (m1 + 1) + (m2 + 1)
2. A. a1[m1] == a2[m2] B. a1[m1] <= a2[m2] C. a1[m1] >= a2[m2] D. a1[m1] != a2[m2]
3. A. left1 == right1 B. left1 < right1 C. left1 > right1 D. left1 != right1
4. A. y = a1[k - left2 - 1] B. y = a1[k - left2] C. y = a2[k - left1 - 1] D. y = a2[k - left1]
5. A. y = a1[k - left2 - 1] B. y = a1[k - left2] C. y = a2[k - left1 - 1] D. y = a2[k - left1]

答案

1~5 CBCCA

解析

我们先把握这份代码的大体思路。令最终第 \(k\) 小的数为 \(x\),记数组 \(a_1\) 中所有小于等于 \(x\) 的数为区间 \([0, k_1]\),数组 \(a_2\) 中所有小于等于 \(x\) 的数为区间 \([0, k_2]\)。则这份代码先通过二分求出 \(k_1\)\(k_2\) 中的某一个,然后答案就容易确定了。

那么如何进行二分呢?我们发现这个二分的方式与我们平时所写的二分不同,各种“+1”、“-1”,甚至根据选项和代码的对称性推测出对于每一个数组,最终 \(\mathrm{left}\)\(\mathrm{right}\) 的值不一样,这使得代码的理解难度高了许多。事实上,我们可以通过观察发现最终作为答案的是 \(\mathrm{left}\) 而不是 \(\mathrm{right}\)。又根据最后返回的下标类似于 \(\mathrm{left} - 1\)\(k - 1\),我们可以推断出 \(\mathrm{left}\) 表示的是数组中小于等于 \(x\) 的元素个数而非下标。此时最后两空的答案就可以确定了:既然一个数组中有 \(\mathrm{left}\) 个元素小于等于 \(x\),那么另一个数组中就有 \(k - \mathrm{left}\) 小于等于 \(x\)。于是,第 4 空选 C,第 5 空选 A。

我们再回过头来看二分。观察到二分边界 \(\mathrm{right}\) 的初值是 \(n - 1\),但由于我们已经知道二分边界表示的是元素个数而不是下标,事实上最开始二分边界也应该包含元素个数为 \(n\) 的情况。又根据后面恶心的“+1”“-1”(注意到并没有某种情况取等),可以推知 \(\mathrm{left}, \mathrm{right}\) 表示的真正含义是:最终小于等于 \(x\) 的元素个数只可能在区间 \([\mathrm{left}, \mathrm{right} + 1]\) 中(注意这里有“+1”)。

然后二分的过程就容易理解了。我们对两个数组分别计算出 \(\mathrm{mid}\),表示我们要通过考虑 \(a_1\) 的前 \(\mathrm{mid}_1\) 个数和 \(a_2\) 的前 \(\mathrm{mid}_2\) 个数来该边二分的边界。在这些数中,我们取出 \(a_1\) 中最大的和 \(a_2\) 中最大的,分别记为 \(s_1\)\(s_2\)。不妨设 \(s_1 \leq s_2\)。如果这些数总共就不足 \(k\) 个,由不等号的传递性,那么所有元素中小于等于 \(s_1\) 的数的个数就更不可能达到 \(k\) 了,因此最终 \(a_1\) 中小于等于 \(x\) 的数的个数一定比当前的 \(\mathrm{mid}_1\) 大,于是有 \(\mathrm{left}_1 \leftarrow \mathrm{mid}_1 + 1\);否则,这些数已经有 \(k\) 个了,那么所有元素中小于等于 \(s_2\) 的数的个数一定不小于 \(k\),因此最终 \(a_2\) 中小于等于 \(x\) 的数的个数一定不超过当前的 \(\mathrm{mid}_2\),于是有 \(\mathrm{right} + 1 \leftarrow \mathrm{mid}_2\),即 \(\mathrm{right}_2 \leftarrow \mathrm{mid}_2 - 1\)

此时容易得出剩余空的答案。代码中 \(\mathrm{cnt}\) 表示 \(a_1\) 的前 \(\mathrm{mid}_1\) 个数和 \(a_2\) 的前 \(\mathrm{mid}_2\) 个数的总共数量,因此第 1 空填 C。第二空就是刚才讨论的情况。刚才我们通过“不妨设”只讨论了一种情况,还剩下另一种,但我们发现 if 里面执行的语句刚好符合刚才讨论的情况,因此第 2 空填 B。在第三空时,我们已经确定某一个数组中小于等于 \(x\) 的数的个数,此时应该有 \(\mathrm{left} = \mathrm{right} + 1\) 表示二分边界保证了只有一种可能的答案,即 \(\mathrm{left} > \mathrm{right}\),因此选 C。

综上,我们解决了此题。

posted @ 2024-09-16 20:14  kilomiles  阅读(118)  评论(0)    收藏  举报