前缀和+哈希:地市级编程竞赛连续子段求和精讲

近期(2026 年 8 月 21 日)铜陵市教体局、科协印发了《关于举办 2026 年铜陵市青少年编程大赛的通知》,比赛设 C++ 项目(小学 / 初中 / 高中组),是地市级算法赛的典型代表。这类比赛里有一类"老熟人"题型——对连续一段数据求和并找特征区间,入门组几乎必考,稍难一点的区赛、市赛也常把它当压轴小问。

很多同学一看到"连续子段"就本能地写两层循环暴力枚举,结果 n 一上 10⁵ 就超时。今天这篇文章用一道原创题,把前缀和 + 哈希表这一招讲透:把 O(n²) 的暴力直接压成 O(n),无论是"最长和为 0 的子段"还是"和为 K 的子段个数"都能一套通吃。

一、题目:科技节摊位人气波动

【背景】 学校科技节连续 n 天对一个摊位做人气打卡,第 i 天的人气净变化为 a[i](可正可负,正数代表新增关注,负数代表流失)。

【问题 A · 基础】 求"人气净变化恰好为 0"的最长连续天数区间长度(即最长的、元素和为 0 的连续子数组长度)。

【问题 B · 进阶】 给定目标整数 K,求"人气累计净增长恰好为 K"的连续区间个数(即元素和恰好等于 K 的连续子数组个数)。

【输入格式】

n K
a[1] a[2] ... a[n]

【数据范围】 1 ≤ n ≤ 2×10⁵;−10⁹ ≤ a[i] ≤ 10⁹;K 为 int 范围内整数。

【样例】

输入:
8 2
3 -1 2 -2 1 -3 1 2

输出:
7 4

解释:问题 A 最长和为 0 的区间是第 2~8 天 [-1, 2, -2, 1, -3, 1, 2],长度 7;问题 B 中和为 2 的子数组有 4 个:[3,-1][2][3,-1,2,-2][2]

二、核心考点拆解

  1. 前缀和(Prefix Sum):用 s[i] 表示前 i 个元素的和,则任意子数组 a[l..r] 的和 = s[r] - s[l-1]。把"子段和"转成了"两个前缀和之差"。
  2. 哈希表记录"首次出现位置":问题 A 要"和为 0",等价于找两个前缀和相等 s[r] == s[l-1],区间长度 = r - (l-1);为了让区间最长,每个前缀和只记它第一次出现的位置。
  3. 哈希表计数:问题 B 要"和为 K",等价于找 s[r] - s[l-1] == K,即之前出现过 s[l-1] == s[r] - K 的次数,用哈希表累加计数。
  4. 边界(空前缀):下标 0 处有一个"和为 0 的空前缀",必须提前放入哈希表,否则会漏掉"从第 1 个元素就开始"的区间。
  5. 数值范围:a[i] 可达 1e9,前缀和需用 long long / int64,否则溢出。
  6. 复杂度跃迁:暴力枚举 O(n²) 在 n=2×10⁵ 时必超时;哈希法 O(n) 轻松过。

三、解法一:最长"和为 0"连续子段

思路:遍历前缀和,若当前和 s 之前见过,说明中间这段和为 0,用"当前下标 − 首次出现下标"更新答案;若没见过才记录位置(只记首次,不更新,这样才能取到最长)。

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<long long> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];

    unordered_map<long long, int> firstPos; // 某前缀和第一次出现的位置
    firstPos[0] = 0;                         // 空前缀,下标 0
    long long s = 0;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        s += a[i - 1];
        if (firstPos.count(s)) {
            ans = max(ans, i - firstPos[s]); // 区间 [firstPos[s]+1, i] 和为 0
        } else {
            firstPos[s] = i;                 // 只记录第一次,保证区间最长
        }
    }
    cout << ans << endl;
    return 0;
}
def longest_zero_sum(arr):
    first_pos = {0: 0}   # 空前缀下标 0
    s = 0
    ans = 0
    for i, x in enumerate(arr, start=1):
        s += x
        if s in first_pos:
            ans = max(ans, i - first_pos[s])
        else:
            first_pos[s] = i  # 只记录第一次出现
    return ans

四、解法二(进阶):子段和恰好为 K 的个数

思路:边走边维护哈希计数 cnt[前缀和]。每读入一个新元素,先看"之前有多少个前缀和等于 s - K",那就是以当前为右端、和为 K 的区间数,累加后再把当前 s 计入哈希。注意顺序:先查询、再加当前 s,否则会把"自己减自己"误算进去。

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

int main() {
    int n; long long K;
    cin >> n >> K;
    vector<long long> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];

    unordered_map<long long, int> cnt;
    cnt[0] = 1;            // 空前缀
    long long s = 0;
    long long ans = 0;
    for (int i = 0; i < n; i++) {
        s += a[i];
        if (cnt.count(s - K)) ans += cnt[s - K]; // 先查历史
        cnt[s]++;                                // 再记当前
    }
    cout << ans << endl;
    return 0;
}
from collections import defaultdict

def count_sum_k(arr, K):
    cnt = defaultdict(int)
    cnt[0] = 1
    s = 0
    ans = 0
    for x in arr:
        s += x
        ans += cnt[s - K]   # 先查历史
        cnt[s] += 1         # 再记当前
    return ans

五、时间 / 空间复杂度

  • 时间复杂度:两个解法都只遍历数组一次,每次哈希操作为均摊 O(1),整体 O(n)
  • 空间复杂度:哈希表最多存 n+1 个不同前缀和,O(n)。相比暴力 O(n²) 既快又省。

六、易错点清单

  1. 忘记放空前缀cnt[0]=1 / firstPos[0]=0 不初始化,会漏掉从第一个元素起就满足条件的区间。
  2. 问题 A 误更新首次位置:如果每次都写 firstPos[s] = i,取到的是"最近一次"而非"第一次",区间变短、答案偏小。正确做法是"见过就跳过,没见过才记录"。
  3. 问题 B 顺序写反:先 cnt[s]++ 再查 s-K,会把当前区间自己当成历史前缀,多算。
  4. 整数溢出:a[i] 与 K 都很大时,前缀和用 32 位 int 会溢出,C++ 务必 long long,Python 无此忧。
  5. 负数取模(进阶坑):若题目改成"子段和能被 m 整除的个数",存 s % m 时 C++ 负数取模为负,需写成 (s % m + m) % m
  6. 数据规模意识:n=2×10⁵ 时,任何 O(n²) 写法(包括看似聪明的枚举起点)都会 TLE,认准 O(n) 哈希法。

七、再进阶:三个方向

  • 方向 1 · 同余前缀和:求"子段和能被 m 整除"的区间个数 → 前缀和取模 + 哈希计数(处理负数取模),是蓝桥杯、电子学会考级里的常客。
  • 方向 2 · 二维前缀和:矩阵中"和恰为 K 的子矩形"个数 → 固定上下边界把矩阵压成一维数组,直接套本题思想。
  • 方向 3 · 哈希去重:求"不同子数组和的种类数" → 把所有前缀和的两两之差塞进集合去重,思路一脉相承。
  • 方向 4 · 与滑动窗口对比:当数组全为非负时,求"和不超过 K 的最长子段"可用双指针滑动窗口;一旦含负数,滑动窗口失效,必须回归前缀和 + 哈希。

八、小结与互动

前缀和 + 哈希表是少儿编程算法赛道里性价比最高的一招:把"连续子段求和"从暴力 O(n²) 一把拉到 O(n),既能解"最长和为 0",也能解"和为 K 的个数",还能横向扩展到同余、二维、去重。建议把上面的样例在本地跑一遍、改改数据自己出几组,真机手感比看十遍都牢。

互动时间:你在地市级 / 区县级编程赛里还遇到过哪些"前缀和"变形题?或者哪一步最容易卡住?欢迎在评论区留言,下一篇我们可以挑"二维前缀和"或"单调队列"接着讲。

原创算法题,转载请注明出处。配套练习与讲解持续更新中。


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青少儿编程课堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值