昨天帮群里一个刚开始刷洛谷的朋友调 P1114“非常男女”计划,他第一版直接暴力枚举,交上去 TLE;第二版改前缀和,但答案一直输出 0。我把代码拿过来扫了一遍,发现问题特别典型:他用来记录“某个前缀和最早出现位置”的数组默认值全是 0,而这个 0 既表示“第 0 个人位置的哨兵”,又表示“这个前缀和还没出现过”。两个含义撞在一起,前缀和 0 刚出现一次就被反复结算,自然不会得到正确答案。
这个题在洛谷的题单里不算难,位置也经常被放在“前缀和与差分”附近,但通过率一直不算高,就是因为里面有不少类似的细节坑。P1114 的核心思路其实是“前缀和 + 哈希 / 数组映射找最早出现位置”,它考察的并不是多高深的算法,而是你有没有把一个现实场景翻译成数学语言的能力。这篇文章我把完整的推导过程、代码细节、常见提交错误都梳理一遍,给正在刷洛谷算法题的朋友做一个参考。无论是准备 CSP-J、还是刚学数据结构想找入门题,这题都非常值得认真做一遍。
1. 题面理解:从“非常男女”到“区间和为 0”
1.1 题目一句话讲清楚
P1114 的原题描述读起来挺有意思,说的是有一排人站在队伍里,每个人用数字标记自己的性别,男生和女生分别对应 1 和 0 之类的一个二进制标记。现在要从队伍里找一段连续的位置,使得这一段里面男生人数和女生人数一样多,然后输出这一段最多能有多少人。
题目给定的数据规模通常是 n 不超过 100000,也就是最多十万个人站在一排。如果找不到任何一段满足条件的连续区间,就输出 0。
光看文字,这好像是一个很偏向统计的问题,但真正动手以后你会发现,它其实是一个典型的区间求和问题。这里的关键词有三个:连续、人数相等、最长。连续说明不能打乱顺序,人数相等说明要比较两类人的数量,最长说明我们希望在满足条件的所有子段里挑一个跨度最大的。这三个条件叠在一起,就决定了不能用简单的全局统计来解决。
1.2 最容易踩进去的直觉误区
很多新手第一次读完题的第一反应是先统计整个队伍里男生有多少、女生有多少,然后觉得答案和全局比例有关系。这个直觉是完全错误的。题目要看的是队伍中某一段连续区间,而不是随便把所有人分成两组。队伍里可能前 100 个人男女各一半,后面还有很多人性别比例完全失衡,但答案只跟前 100 个人这段有关,全局统计在这里起不到任何帮助。
还有人会想,既然只有两种性别,那我能不能排序后再找?这也不行。排序会破坏原有的连续顺序。题目说的“连续位置”,本质上是一种区间概念。你可以把它想象成在一串已经排好的数据上切一刀,拿出中间完整的一段,而不是允许你重新排列组合。
我见过不少初学者卡在第二步:枚举所有区间左端点和右端点,然后每个区间都重新数里面男生和女生的人数。这个做法逻辑上没错,但复杂度直接爆炸。后面我会详细算这笔账。
1.3 把“男女数量相等”翻译成等式
假设我们在考虑某一段连续区间 [l, r],这里 l 是左端点位置,r 是右端点位置。这一段里男生数量记为 man,女生数量记为 woman。题目要求 man = woman。
这里可以做一个小小但非常关键的转化:把男生看成数值 +1,把女生看成数值 -1。那么这段区间的元素和就是:
man × (+1) + woman × (-1) = man - woman
因为题目要求 man = woman,所以区间和必须等于 0。换句话说,“这段区间里男女数量相等”和“这段区间的加权和为 0”是完全等价的。
为什么要做这个转化?因为“男生数量等于女生数量”是一个由两个变量组成的条件,而把它变成“男减女等于 0”之后,就变成了单一指标。连续区间的“和”是非常适合用前缀和来维护的数据。一个二元条件被压缩成了一元数值,问题一下子简单了很多。
这里可以用一个生活化的类比来帮助理解。想象你在记录自己体重的净增长:体重增加记 +1,体重减少记 -1。如果某一天你的体重和一个月前完全一样,那么中间这段时间里增加的重量和减少的重量一定刚好抵消。性别序列也是一样,开头和结尾前缀和相等的话,中间这段正负号的数量就恰好抵消。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解法的递进:从 O(n³) 到 O(n)
2.1 暴力枚举为什么注定过不了
最直观的做法是枚举所有可能的区间 [l, r],然
