|
| 1 | +# 凹语言中文版NOIP 2024真题详解第1题 |
| 2 | + |
| 3 | +- 时间:2025-11-12 |
| 4 | +- 撰稿:凹语言开发组 |
| 5 | +- 转载请注明原文链接:[https://wa-lang.org/smalltalk/st0094.html](https://wa-lang.org/smalltalk/st0094.html) |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +NOIP 是全国青少年信息学奥林匹克联赛的英文缩写,是面向中小学生的信息学竞赛。由中国计算机学会主办,是国内信息学领域权威的普及性竞赛。主要考察编程能力和算法思维,核心语言包括 C++(主流)、C、Pascal(部分组别)。参赛对象为小学、初中、高中学生,分提高组、普及组等组别。成绩优秀者可获得高校自主招生、综合评价等相关优惠,是信息学特长升学的重要参考。 |
| 10 | + |
| 11 | +作为中国乃至面向世界的赛事,NOIP 比赛使用的全部是国外的编程语言。本文尝试通过凹语言来讲解答,通过不同的视角体会算法问题和工程问题的差异。 |
| 12 | + |
| 13 | +## 1. NOIP 2024 第1题 |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +如果暂时不考虑性能(即 $O(n^2)$ 甚至更慢的复杂度也没关系),我们能找到一个**更直观、更容易理解**的算法,这个算法被称为**最大流/最小费用最大流**模型,或者用更简单的语言描述:**最大二分图匹配**的变形。 |
| 19 | + |
| 20 | +但是,因为这个题目的特殊性质(只有 '0' 和 '1' 两种字符,且操作是相邻交换),我们可以避开复杂的图论模型,直接使用**纯粹的贪心和计数**,这是最容易理解且最高效的方法。 |
| 21 | + |
| 22 | +## 2. 💡 最易理解的算法:纯粹的贪心与计数 |
| 23 | + |
| 24 | +这个算法的思路是:**将问题分解为独立的两部分,并分别用贪心法解决。** |
| 25 | + |
| 26 | +### 2.1. 核心洞察:字符的分类与自由度 |
| 27 | + |
| 28 | +所有的字符和位置都可以根据它们能否被交换,被分成四种类型: |
| 29 | + |
| 30 | +| $s_1$ 的状态 ($t_{1,i}$) | $s_2$ 的状态 ($t_{2,i}$) | 位置 $i$ 的自由度 | 决策:匹配 $s_{1,i}$ 和 $s_{2,i}$ | |
| 31 | +| :----------------------: | :----------------------: | :----------------: | :-------------------------------: | |
| 32 | +| **0 (固定)** | **0 (固定)** | 0 | **固定匹配/不匹配。** $s_{1,i}$ 必须对 $s_{2,i}$。 | |
| 33 | +| **0 (固定)** | **1 (可换)** | 1 | $s_{1,i}$ 确定。用 $s_2$ 的**可换字符池**去匹配它。 | |
| 34 | +| **1 (可换)** | **0 (固定)** | 1 | $s_{2,i}$ 确定。用 $s_1$ 的**可换字符池**去匹配它。 | |
| 35 | +| **1 (可换)** | **1 (可换)** | 2 | **完全自由。** 用 $s_1, s_2$ **剩余**的字符池进行自由组合。 | |
| 36 | + |
| 37 | +### 2.2. 算法核心:两个独立的池子 |
| 38 | + |
| 39 | +因为相邻交换的特性(只要没有 $t_{i}=0$ 的障碍物,可换字符可以在其区间内任意排列),我们可以把所有 $t_{1,i}=1$ 的字符**想象成一个大池子 $C_1$**,所有 $t_{2,i}=1$ 的字符**想象成一个大池子 $C_2$**。 |
| 40 | + |
| 41 | +**目标:** 最大化从 $C_1$ 和 $C_2$ 中取出相同字符进行匹配的次数。 |
| 42 | + |
| 43 | +### 2.3. 三步贪心策略 |
| 44 | + |
| 45 | +我们将匹配过程分为三步,每一步都采用贪心策略,因为一旦完成了匹配,这个字符就不能再被使用了(即:**先用先得**)。 |
| 46 | + |
| 47 | +#### 步骤 1:处理双固定的位置(类型 0) |
| 48 | + |
| 49 | +* **操作:** 遍历所有 $i$,如果 $t_{1,i}=0$ 且 $t_{2,i}=0$。 |
| 50 | +* **贪心:** 如果 $s_{1,i} = s_{2,i}$,这是一个**确定无疑的匹配**。 |
| 51 | +* **效果:** 答案 $+1$。 |
| 52 | + |
| 53 | +#### 步骤 2:处理一固一换的位置(类型 1) |
| 54 | + |
| 55 | +* **操作:** 遍历所有 $i$ 属于类型 1 的位置($t_{1,i} \neq t_{2,i}$)。 |
| 56 | +* **贪心:** 在这类位置上,我们必须用 $s_1$ 或 $s_2$ 中固定的字符去**锁定**匹配目标,然后**消耗**掉另一个字符串的字符池中的一个对应字符。 |
| 57 | + * **例:** $t_{1,i}=0, t_{2,i}=1$ 且 $s_{1,i} = '0'$。如果 $C_2$ 池子中还有 '0',我们**必须**用 $C_2$ 中的一个 '0' 来匹配这个固定的 $s_{1,i}$,从而锁定这个匹配。 |
| 58 | +* **效果:** |
| 59 | + * 如果匹配成功,答案 $+1$。 |
| 60 | + * 相应地,从 $C_1$ 或 $C_2$ 中**减少**对应字符的计数。 |
| 61 | + |
| 62 | +**为什么先处理这个?** 因为这些固定字符是**必须**要匹配的(如果可能),它们消耗了 $C_1$ 或 $C_2$ 的资源,限制了接下来的自由组合。 |
| 63 | + |
| 64 | +#### 步骤 3:处理双可换的位置(类型 2) |
| 65 | + |
| 66 | +* **操作:** 此时 $C_1$ 和 $C_2$ 池子中还剩下一些字符,以及一些 $t_{1,i}=1, t_{2,i}=1$ 的**空闲位置**。 |
| 67 | +* **贪心:** 由于这些空闲位置是完全自由的,我们可以用 $C_1$ 中**剩余**的字符和 $C_2$ 中**剩余**的字符进行**最大化匹配**。 |
| 68 | + * **匹配 '0':** 我们可以组成 $\min(\text{剩余 '0' in } C_1, \text{剩余 '0' in } C_2)$ 对。 |
| 69 | + * **匹配 '1':** 我们可以组成 $\min(\text{剩余 '1' in } C_1, \text{剩余 '1' in } C_2)$ 对。 |
| 70 | +* **效果:** 答案加上这些匹配的总和。 |
| 71 | + |
| 72 | + |
| 73 | +## 3. 开始解决正题 |
| 74 | + |
| 75 | +这个算法把一个复杂的排列问题,简化成了**资源分配**问题: |
| 76 | + |
| 77 | +1. **确定**不需要资源的匹配。 |
| 78 | +2. **贪心**分配资源($C_1$ 和 $C_2$)给那些半固定的需求。 |
| 79 | +3. **最大化**利用剩余的资源进行自由组合。 |
| 80 | + |
| 81 | +由于我们总是在可能的情况下优先匹配(贪心),且我们没有丢失任何字符($C_1$ 和 $C_2$ 中包含了所有可换字符),这个策略是正确的。 |
| 82 | + |
| 83 | +凹语言中文编程的代码片段如下: |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | +完整的代码:[https://gitcode.com/wa-lang/wa/blob/master/waroot/examples/noip2024/edit.wz](https://gitcode.com/wa-lang/wa/blob/master/waroot/examples/noip2024/edit.wz) |
| 88 | + |
| 89 | + |
| 90 | +## 4. 结语:在实战中打磨中文编程 |
| 91 | + |
| 92 | +之前我们已经通过凹语言中文编程展示了CSP-J题目的解法,今天我们更进一步给出了NOIP题目的解法。正如我国回归联合国的过程充满了曲折,中文编程想走向世界的道路也注定不会平坦。最终的结果是汉语成了联合国的官方工作语言之一,中文编程也必将在世界上占有一席之地。 |
| 93 | + |
0 commit comments