文章

【GESP】C++ 五级真题解析,[2025年12月,第十二次认证]第一题-数字移动 luogu-p14917

GESP C++ 2025年12月,五级真题第一题,考察二分答案算法思想,在历届真题中属于相对少见的,对考试来说有一定难度。题目难度⭐⭐⭐☆☆。洛谷难度等级普及/提高−

P14917 [GESP202512 五级] 数字移动

题目要求

题目描述

小 A 有一个包含 $N$ 个正整数的序列 $A={A_1,A_2,\cdots,A_N}$,序列 $A$ 恰好包含 $\frac{N}{2}$ 对不同的正整数。形式化地,对于任意 $1 \le i \le N$,存在唯一一个 $j$ 满足 $1\le j \le N, i\neq j, A_i=A_j$。

小 A 希望每对相同的数字在序列中相邻,为了实现这一目的,小 A 每次操作会选择任意 $i(1\le i\le N)$,将当前序列的第 $i$ 个数字移动到任意位置,并花费对应数字的体力。

例如,假设序列 $A={1,2,1,3,2,3}$,小 A 可以选择 $i=2$,将 $A_2=2$ 移动到 $A_3=1$ 的后面,此时序列变为 ${1,1,2,3,2,3}$,耗费 $2$ 点体力。小 A 也可以选择 $i=3$,将 $A_3=1$ 移动到 $A_2=2$ 的前面,此时序列变为 ${1,1,2,3,2,3}$,花费 $1$ 点体力。

小 A 可以执行任意次操作,但他希望自己每次花费的体力尽可能小。小 A 希望你能帮他计算出一个最小的 $x$,使得他能够在每次花费的体力均不超过 $x$ 的情况下令每对相同的数字在序列中相邻。

输入格式

第一行一个正整数 $N$,代表序列长度,保证 $N$ 为偶数。

第二行包含 $N$ 个正整数 $A_1,A_2,\ldots,A_N$,代表序列 $A$。且对于任意 $1\le i\le N$,存在唯一一个 $j$ 满足 $1\le j\le N,i\neq j,A_i=A_j$。

数据保证小 A 至少需要执行一次操作。

输出格式

输出一行,代表满足要求的 $x$ 的最小值。

输入输出样例 #1

输入 #1
1
2
6
1 2 1 3 2 3
输出 #1
1
2

说明/提示

对于 $40\%$ 的测试点,保证 $1\le N,A_i\le 100$。

对于所有测试点,保证 $1\le N,A_i\le 10^5$。


题目分析

本题的核心在于理解“移动代价”与“位置重排”之间的关系。

1. 核心思路:二分答案

题目要求找到一个最小的代价上限 $x$,使得我们只移动所有 代价值 $\le x$ 的数字,就能让所有相同的数字相邻。

  • 单调性:如果花费上限 $x$ 可以满足条件,那么花费上限 $x+1$ 一定也能满足(因为可移动的数字更多了)。反之,如果 $x$ 不满足,那么 $x-1$ 也一定不满足。
  • 算法选择:这种具有单调性的“最小化最大值”或“最小化代价”的问题,非常适合使用 二分答案(Binary Search on Answer)。

我们不需要直接构造出移动方案,只需要在一个范围内二分查找这个代价 $x$,然后写一个 check(x) 函数来验证是否可行。

2. 验证策略:贪心思想

对于给定的代价上限 $x$:

  • 可移动元素:凡是数值 $\le x$ 的元素,都可以消耗 $\le x$ 的体力任意移动。在逻辑上,我们可以认为它们是“液态”的,可以被抽离并插入到任何位置。只要剩下的“骨架”合法,这些数字总能塞回去。
  • 不可移动元素:凡是数值 $> x$ 的元素,因为移动它需要消耗 $> x$ 的体力,超过了上限,所以它们必须保持原有的相对位置不动。

判定逻辑

我们将序列中所有 $\le x$ 的数全部“拿走”,只留下 $> x$ 的数(保持相对顺序不变)。 剩下的这个子序列(我们称为“骨架”),必须满足“相邻元素两两相等”的结构(即 A A B B C C 的形式)。

  • 如果不满足,说明这些不可移动的大数卡住了位置,导致无法配对,该 $x$ 不可行。
  • 如果满足,说明大数已经归位,我们可以把之前拿走的小数($\le x$)成对地插回骨架的缝隙中,该 $x$ 可行。

例如:序列 5 8 5 8,如果 $x=4$,则 58都不能动,剩 5 8 5 8,显然不满足 5 5 8 88 8 5 5,失败。 例如:序列 1 5 1 5,如果 $x=3$,1可以动,5不能动。拿走 1 后剩 5 5,满足条件。我们可以把 1 1 插在 5 5 前面或后面。

3. 算法步骤

  1. 确定二分范围:下界 $L$ 为数组中的最小值,上界 $R$ 为数组中的最大值。
  2. 二分查找
    • 取中间值 $mid$。
    • 调用 check(mid) 验证。
    • 如果可行,说明代价 $mid$ 足够(甚至可能偏大),尝试更小的代价,更新 $R = mid$。
    • 如果不可行,说明代价太小,需要更大的代价,更新 $L = mid + 1$。
  3. 编写 check(x)
    • 遍历原数组,将所有 $> x$ 的数按顺序存入新数组 fixed
    • 检查 fixed 数组:第 0 和 1 个是否相等,第 2 和 3 个是否相等……以此类推。
    • 如果所有对子都匹配,返回 true,否则返回 false。

4. 复杂度分析

  • 时间复杂度:二分查找进行 $O(\log (\max A_i))$ 次,每次 check 需要遍历数组 $O(N)$。总时间复杂度为 $O(N \log (\max A_i))$。在本题 $N=10^5$ 的数据规模下,计算量约为 $1.7 \times 10^6$,完全可以在 1 秒内通过。
  • 空间复杂度:需要 $O(N)$ 的额外空间来存储 check 过程中的临时数组。


示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <algorithm>
#include <climits>
#include <iostream>
#include <vector>

int a[100005];
int N;

// 检查函数:判断当保留大于 x 的元素时,是否满足“相邻元素两两相等”的条件(比如 2
// 2 5 5)
bool check(int x) {
    // 定义一个向量 fixed,用于存储所有大于阈值 x 的元素
    std::vector<int> fixed;
    for (int i = 1; i <= N; i++) {
        // 遍历原数组,筛选出大于 x 的数
        if (a[i] > x) {
            fixed.push_back(a[i]);
        }
    }

    // 遍历筛选后的数组,每次步进 2,检查相邻两个元素是否相等
    // 根据题目数据,fixed.size() 一定是偶数,所以不会越界
    for (int i = 0; i < fixed.size(); i += 2) {
        if (fixed[i] != fixed[i + 1]) {
            return false;  // 如果这一对不相等,说明不能完美配对,返回 false
        }
    }
    return true;
}
int main() {
    std::cin >> N;
    int max = INT_MIN;
    int min = INT_MAX;
    for (int i = 1; i <= N; i++) {
        std::cin >> a[i];
        max = std::max(max, a[i]);
        min = std::min(min, a[i]);
    }

    // 二分查找
    // 目标:寻找满足 check(x) == true 的最小 x
    // 也就是找到一个最小的阈值,使得消除 <= 阈值的数后,剩下的数能组成对子。
    int l = min;
    int r = max;
    while (l < r) {
        int mid = l + (r - l) / 2;  // 防止 (l+r) 溢出,且向下取整
        if (check(mid)) {
            // 如果 mid 满足条件,尝试更小的 x,看能不能更小
            // 答案可能是 mid,也可能在左边
            r = mid;
        } else {
            // 如果 mid 不满足,说明阈值太小了,消除的数不够多(或者消除的不对)
            // 答案一定在 mid 右边,mid 本身肯定不行
            l = mid + 1;
        }
    }

    std::cout << l << '\n';
    return 0;
}

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权