备战蓝桥杯国赛【day3】

今日题单概览

题目 核心技巧 难度 收获等级
幂次区间和查询 多维度前缀和预处理 ⭐⭐⭐ 💡💡💡
小郑的蓝桥平衡串 前缀和 + 哈希表 ⭐⭐⭐ 💡💡💡💡
统计子矩阵 二维前缀和 + 枚举优化 ⭐⭐⭐⭐ 💡💡💡💡💡

一、幂次区间和查询:前缀和的"多维"思维

题目回顾

给定数组 aaa 和 mmm 个查询,每个查询问 [l,r][l,r][l,r] 区间内所有元素的 kkk 次方和(k≤5k \leq 5k≤5),对 109+710^9+7109+7 取模。

数据规模 :n,m≤105n, m \leq 10^5n,m≤105,要求高效处理。

核心洞察:kkk 的范围很小!

kkk 只有 1~5 五种可能。这意味着我们可以预处理 5 个前缀和数组 ,查询时直接 O(1)O(1)O(1) 回答。

python 复制代码
import sys
from itertools import accumulate

input = sys.stdin.readline
n, m = map(int, input().split())
a = list(map(int, input().split()))
mod = 10**9 + 7

def get_prefix_sum(arr):
    """构建前缀和数组,带取模防溢出"""
    prefix = [0]
    for x in arr:
        prefix.append((prefix[-1] + x) % mod)
    return prefix

def range_sum(prefix, l, r):
    """查询[l,r]区间和,0-indexed"""
    return (prefix[r + 1] - prefix[l] + mod) % mod

# 预处理 k=1,2,3,4,5 的前缀和
prefix_powers = []
for k in range(1, 6):
    powered = [pow(x, k, mod) for x in a]  # 先取模防溢出!
    prefix_powers.append(get_prefix_sum(powered))

# 处理查询
for _ in range(m):
    l, r, k = map(int, input().split())
    # 转为 0-indexed
    ans = range_sum(prefix_powers[k - 1], l - 1, r - 1)
    print(ans)

复杂度分析

指标 复杂度 说明
预处理 O(5⋅n)O(5 \cdot n)O(5⋅n) 5次遍历,每次O(n)
单次查询 O(1)O(1)O(1) 直接数组访问
总查询 O(m)O(m)O(m) m次O(1)
空间 O(5⋅n)O(5 \cdot n)O(5⋅n) 5个前缀和数组

💡 关键细节pow(x, k, mod)x**k % mod 更高效且不会溢出,这是 Python 的模幂优化。

思维跃迁:如果 kkk 很大怎么办?

如果 k≤109k \leq 10^9k≤109,预处理就不现实了。这时需要数学武器

  • 费马小定理 :若 ppp 是质数,ap−1≡1(modp)a^{p-1} \equiv 1 \pmod{p}ap−1≡1(modp),可降幂
  • 欧拉定理:更一般的降幂公式
  • 线段树 + 快速幂:单点修改+区间查询时用

但今天这道题告诉我们:看清数据范围的约束,往往比盲目上高级数据结构更重要


二、小郑的蓝桥平衡串:前缀和 + 哈希表的黄金组合

题目回顾

只含 LQ 的字符串,找最长的平衡子串(LQ 数量相等)。

样例LQLL → 最长平衡串是 LQ,长度为 2。

暴力解法的问题

python 复制代码
# O(n^2) 暴力------对于 len≤1000 可过,但不够优雅
for i in range(n):
    for j in range(i, n):
        if s[i:j+1].count('L') == s[i:j+1].count('Q'):
            ans = max(ans, j - i + 1)

优雅解法:前缀和 + 哈希映射

核心转化 :把 L 看作 +1,Q 看作 -1。问题转化为:找最长的子数组,使其和为 0

python 复制代码
from itertools import accumulate

s = input()
n = len(s)

# 转化:L -> +1, Q -> -1
diff = [1 if c == 'L' else -1 for c in s]

# 前缀和
prefix = [0] + list(accumulate(diff))

# 关键:如果 prefix[j+1] == prefix[i],说明 diff[i:j+1] 的和为0
# 即 s[i:j+1] 中 L 和 Q 数量相等!

ans = 0
# 用哈希表记录每个前缀和值第一次出现的位置
first_occurrence = {0: 0}  # prefix[0] = 0 出现在索引0

for i in range(1, n + 1):
    if prefix[i] in first_occurrence:
        # 找到了一个平衡子串!
        length = i - first_occurrence[prefix[i]]
        ans = max(ans, length)
    else:
        # 只记录第一次出现的位置,保证子串最长
        first_occurrence[prefix[i]] = i

print(ans)

算法可视化

复制代码
字符串:  L   Q   L   L
diff:    1  -1   1   1
prefix:  0   1   0   1   2
          ↑       ↑
         位置0   位置2
         
prefix[0] == prefix[2] == 0
说明 diff[0:2] = [1, -1] 和为0,即 "LQ" 是平衡串,长度2

为什么哈希表要记录"第一次出现"?

因为我们要最长的 子串。如果同一个前缀和值在位置 iii 和 jjj(i<ji < ji<j)都出现,那么 [i,j−1][i, j-1][i,j−1] 就是一个平衡子串。记录第一次出现,就能保证 j−ij - ij−i 最大。

复杂度对比

解法 时间 空间 适用场景
暴力枚举 O(n2)O(n^2)O(n2) O(1)O(1)O(1) n≤103n \leq 10^3n≤103
前缀和+哈希 O(n)O(n)O(n) O(n)O(n)O(n) n≤106n \leq 10^6n≤106
前缀和+暴力找 O(n2)O(n^2)O(n2) O(n)O(n)O(n) 不推荐

💡 模式识别 :"找和为0的最长子数组"是经典题型,前缀和+哈希表是标准解法。类似的还有"和为K的子数组个数"等。


三、统计子矩阵:二维前缀和的降维打击

题目回顾

给定 N×MN \times MN×M 矩阵,统计有多少个子矩阵的元素和 ≤K\leq K≤K。

数据规模 :N,M≤500N, M \leq 500N,M≤500,K≤2.5×108K \leq 2.5 \times 10^8K≤2.5×108。

核心武器:二维前缀和

首先复习二维前缀和的构建:

python 复制代码
n, m, k = map(int, input().split())

# 下标从1开始,方便处理边界
a = [[0] * (m + 1) for _ in range(n + 1)]
prefix = [[0] * (m + 1) for _ in range(n + 1)]

# 输入
for i in range(1, n + 1):
    row = list(map(int, input().split()))
    for j in range(1, m + 1):
        a[i][j] = row[j - 1]

# 构建二维前缀和
for i in range(1, n + 1):
    for j in range(1, m + 1):
        prefix[i][j] = (prefix[i-1][j] + prefix[i][j-1] 
                       - prefix[i-1][j-1] + a[i][j])

子矩阵和查询公式

复制代码
(x1,y1) 到 (x2,y2) 的子矩阵和 =
    prefix[x2][y2] 
  - prefix[x1-1][y2] 
  - prefix[x2][y1-1] 
  + prefix[x1-1][y1-1]

图示理解:

复制代码
+-----------+-----------+
|           |           |
|  区域A    |  区域B    |
|           |           |
+-----------+-----------+
|           |           |
|  区域C    |  目标区域  |
|           |  (x2,y2)  |
+-----------+-----------+
(x1,y1)

完整解法:枚举 + 二维前缀和

python 复制代码
def get_submatrix_sum(x1, y1, x2, y2):
    """获取子矩阵和,1-indexed,包含边界"""
    return (prefix[x2][y2] 
          - prefix[x1-1][y2] 
          - prefix[x2][y1-1] 
          + prefix[x1-1][y1-1])

ans = 0

# 枚举左上角 (x1, y1)
for x1 in range(1, n + 1):
    for y1 in range(1, m + 1):
        # 枚举右下角 (x2, y2)
        for x2 in range(x1, n + 1):
            for y2 in range(y1, m + 1):
                if get_submatrix_sum(x1, y1, x2, y2) <= k:
                    ans += 1

print(ans)

复杂度分析

指标 复杂度 说明
构建前缀和 O(N⋅M)O(N \cdot M)O(N⋅M) 双重循环
枚举子矩阵 O(N2⋅M2)O(N^2 \cdot M^2)O(N2⋅M2) 四重循环
单次查询 O(1)O(1)O(1) 公式计算
总时间 O(N2⋅M2)O(N^2 \cdot M^2)O(N2⋅M2) 当 N=M=500N=M=500N=M=500 时,约 6.25×10106.25 \times 10^{10}6.25×1010

等等! 5004=6.25×1010500^4 = 6.25 \times 10^{10}5004=6.25×1010,这显然会超时!

优化思路:降维 + 双指针/二分

对于 N,M≤500N, M \leq 500N,M≤500,O(N2⋅M2)O(N^2 \cdot M^2)O(N2⋅M2) 确实太大。我们需要优化到 O(N2⋅M)O(N^2 \cdot M)O(N2⋅M) 或更优。

优化策略 :固定上下两行,转化为"一维数组中子数组和 ≤K\leq K≤K 的个数"问题。

python 复制代码
ans = 0

# 枚举上边界 top
for top in range(1, n + 1):
    # row_sum[j] 表示从 top 到 bottom 行,第 j 列的累加和
    row_sum = [0] * (m + 1)
    
    # 枚举下边界 bottom
    for bottom in range(top, n + 1):
        # 更新 row_sum
        for j in range(1, m + 1):
            row_sum[j] += a[bottom][j]
        
        # 现在问题转化为:一维数组 row_sum[1..m] 中,
        # 有多少个子数组的和 <= K?
        # 可以用前缀和 + 有序集合/树状数组优化到 O(m log m)
        # 或者用双指针(如果元素非负)优化到 O(m)
        
        # 由于 a[i][j] >= 0,row_sum 也是非负的!
        # 可以用双指针(滑动窗口)
        
        prefix_1d = [0]
        for j in range(1, m + 1):
            prefix_1d.append(prefix_1d[-1] + row_sum[j])
        
        # 双指针:找满足 prefix_1d[r] - prefix_1d[l] <= K 的 (l,r) 对数
        left = 0
        for right in range(1, m + 1):
            while prefix_1d[right] - prefix_1d[left] > k:
                left += 1
            ans += right - left

print(ans)

优化后的复杂度

  • 枚举上下边界:O(N2)O(N^2)O(N2)
  • 每次双指针:O(M)O(M)O(M)
  • 总复杂度:O(N2⋅M)≈5002×500=1.25×108O(N^2 \cdot M) \approx 500^2 \times 500 = 1.25 \times 10^8O(N2⋅M)≈5002×500=1.25×108,可接受!

💡 关键观察 :题目中 Aij≥0A_{ij} \geq 0Aij≥0,这是能用双指针优化的前提条件 。如果允许负数,就需要用前缀和 + 有序集合(平衡树)树状数组/线段树 来优化,复杂度为 O(N2⋅Mlog⁡M)O(N^2 \cdot M \log M)O(N2⋅MlogM)。


四、今日心法:前缀和的三重境界

复制代码
第一重:一维前缀和
    区间求和 → O(1) 查询
    公式:sum[l,r] = prefix[r] - prefix[l-1]
    
第二重:前缀和 + 哈希表  
    子数组和为K / 最长平衡子串 → O(n) 搞定
    关键:和值 -> 位置的映射
    
第三重:二维前缀和 + 降维优化
    子矩阵统计 → 枚举 + 双指针 / 有序集合
    关键:固定一维,转化为一维问题

五、代码模板速查

模板1:一维前缀和(带取模)

python 复制代码
def build_prefix(a, mod=10**9+7):
    prefix = [0]
    for x in a:
        prefix.append((prefix[-1] + x) % mod)
    return prefix

def range_query(prefix, l, r, mod=10**9+7):
    # [l, r] 都是 0-indexed,闭区间
    return (prefix[r + 1] - prefix[l] + mod) % mod

模板2:二维前缀和

python 复制代码
def build_2d_prefix(a, n, m):
    prefix = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            prefix[i][j] = (prefix[i-1][j] + prefix[i][j-1] 
                          - prefix[i-1][j-1] + a[i][j])
    return prefix

def submatrix_sum(prefix, x1, y1, x2, y2):
    # 1-indexed,包含边界
    return (prefix[x2][y2] - prefix[x1-1][y2] 
          - prefix[x2][y1-1] + prefix[x1-1][y1-1])

模板3:前缀和 + 哈希表(最长平衡子串)

python 复制代码
from itertools import accumulate

def longest_balanced(s, char_pos, char_neg):
    diff = [1 if c == char_pos else -1 for c in s]
    prefix = [0] + list(accumulate(diff))
    
    first = {0: 0}
    ans = 0
    for i in range(1, len(prefix)):
        if prefix[i] in first:
            ans = max(ans, i - first[prefix[i]])
        else:
            first[prefix[i]] = i
    return ans

写在最后

今天的刷题让我深刻体会到:前缀和不是技巧,而是一种思想

它教会我们:

  1. 空间换时间 :用 O(n)O(n)O(n) 空间预处理,换取 O(1)O(1)O(1) 查询
  2. 转化思想:把"区间和"转化为"两点差值"
  3. 降维打击:二维问题固定一维,化为一维问题

"算法之美,在于发现隐藏的结构。前缀和,就是发现'累积'结构的艺术。"


如果你也在刷题路上,欢迎交流。记住:看懂题解只是开始,能写出模板、能变形应用、能在面试中讲清楚,才是真正的掌握。 🚀

相关推荐
码农阿豪1 小时前
Python 操作金仓数据库的完全指南(下篇):SQL执行、批量操作与扩展功能
数据库·python·sql
曲幽1 小时前
用了loguru我才明白,Python日志还能这么写
python·logging·fastapi·web·async·loguru·handler·uvicorn
小糖学代码1 小时前
LLM系列:2.pytorch入门:9.神经网络的学习
人工智能·python·深度学习·神经网络·学习·机器学习
曾凡玉@1 小时前
Python 并发编程系统笔记
开发语言·笔记·python
测试19981 小时前
接口测试工具:Postman的高级用法
自动化测试·软件测试·python·测试工具·测试用例·接口测试·postman
2501_901200532 小时前
mysql数据库主键类型对性能的影响_使用自增整数优于UUID
jvm·数据库·python
.柒宇.2 小时前
FastAPI进阶教程
开发语言·python·fastapi
张立立2 小时前
震惊!用Python每天早上8点,我准时给女神发早安,只因这个脚本…
后端·python