备战蓝桥杯国赛【Day 11】

📚 今日刷题清单

题号 题目 类型 难度 核心考点
1 求和 前缀和 ⭐⭐⭐ 数学转化、前缀和优化
2 子串简写 前缀和 + 计数 ⭐⭐⭐ 前缀和统计、区间条件
3 帕鲁服务器 前缀和 + 区间查询 ⭐⭐⭐ 状态转移、前缀和数组
4 闪耀的灯光 二维前缀和 ⭐⭐⭐⭐ 二维前缀和、周期取模
5 重新排序 差分 + 贪心排序 ⭐⭐⭐⭐⭐ 差分数组、贡献排序

一、前缀和与差分核心思想

1.1 一维前缀和

python 复制代码
# 构建前缀和数组
pre = [0] * (n + 1)
for i in range(1, n + 1):
    pre[i] = pre[i - 1] + a[i]

# 区间查询 [l, r] 的和
sum(l, r) = pre[r] - pre[l - 1]

1.2 一维差分

python 复制代码
# 构建差分数组
diff = [0] * (n + 2)
diff[l] += x
diff[r + 1] -= x

# 还原原数组
for i in range(1, n + 1):
    a[i] = a[i - 1] + diff[i]

1.3 二维前缀和

python 复制代码
# 构建二维前缀和
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]

# 矩形区域查询 [x1,y1] 到 [x2,y2]
sum = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]

二、例题精讲

例题 1:求和 ⭐数学转化

项目 内容
类型 前缀和 + 数学
核心 S=∑i<jai⋅aj=∑iai⋅(∑j>iaj)S = \sum_{i<j} a_i \cdot a_j = \sum_{i} a_i \cdot (\sum_{j>i} a_j)S=∑i<jai⋅aj=∑iai⋅(∑j>iaj)
题目描述

给定 nnn 个整数,求它们两两相乘再相加的和:
S=a1⋅a2+a1⋅a3+⋯+an−1⋅anS = a_1 \cdot a_2 + a_1 \cdot a_3 + \cdots + a_{n-1} \cdot a_nS=a1⋅a2+a1⋅a3+⋯+an−1⋅an

关键思路

暴力做法 :双重循环 O(n2)O(n^2)O(n2),n≤200000n \leq 200000n≤200000 会超时!

优化思路:利用前缀和

S=∑i=1n−1ai⋅(ai+1+ai+2+⋯+an)S = \sum_{i=1}^{n-1} a_i \cdot (a_{i+1} + a_{i+2} + \cdots + a_n)S=i=1∑n−1ai⋅(ai+1+ai+2+⋯+an)

维护后缀和 sufi=ai+1+ai+2+⋯+ansuf_i = a_{i+1} + a_{i+2} + \cdots + a_nsufi=ai+1+ai+2+⋯+an,则:
S=∑i=1n−1ai⋅sufiS = \sum_{i=1}^{n-1} a_i \cdot suf_iS=i=1∑n−1ai⋅sufi

推演验证
复制代码
输入: n=4, a=[1, 3, 6, 9]

暴力计算:
1*3 + 1*6 + 1*9 + 3*6 + 3*9 + 6*9
= 3 + 6 + 9 + 18 + 27 + 54 = 117 ✓

前缀和优化:
s1 = sum(a) = 19

i=0: s1 -= a[0] = 19-1 = 18, S += 1*18 = 18
i=1: s1 -= a[1] = 18-3 = 15, S += 3*15 = 45 (S=63)
i=2: s1 -= a[2] = 15-6 = 9,  S += 6*9 = 54 (S=117)
i=3: s1 -= a[3] = 9-9 = 0,  S += 9*0 = 0 (S=117)

输出: 117 ✓
题解
python 复制代码
n = int(input())
S = 0
a = list(map(int, input().split()))
s1 = sum(a)  # 后缀和

for i in range(0, n):
    s1 -= a[i]           # 减去当前元素,得到后面所有元素的和
    S += a[i] * s1       # 当前元素与后面所有元素的乘积之和

print(S)

复杂度 :时间 O(n)O(n)O(n),空间 O(1)O(1)O(1)

关键细节
坑点 说明
先减后乘 s1 -= a[i] 要在 S += a[i] * s1 之前执行
数据类型 n≤200000,ai≤1000n \leq 200000, a_i \leq 1000n≤200000,ai≤1000,结果可能很大,Python自动处理大整数
后缀和思路 维护当前元素之后所有元素的和,避免双重循环

例题 2:子串简写 ⭐前缀和计数

项目 内容
类型 前缀和 + 字符串
核心 统计以 c1c_1c1 开头、c2c_2c2 结尾且长度 ≥K\geq K≥K 的子串数量
题目描述

给定字符串 SSS 和字符 c1,c2c_1, c_2c1,c2,统计有多少个子串满足:

  • 以 c1c_1c1 开头,以 c2c_2c2 结尾
  • 长度 ≥K\geq K≥K(才能使用简写)
关键思路

暴力做法 :枚举所有子串 O(n2)O(n^2)O(n2),n≤5×105n \leq 5 \times 10^5n≤5×105 会超时!

前缀和优化

  • pre[i] 记录到位置 iii 为止,c1c_1c1 出现的次数
  • 对于每个 c2c_2c2 出现的位置 iii,找前面位置 jjj 满足:
    • S[j]=c1S[j] = c_1S[j]=c1
    • 子串长度 i−j+1≥Ki - j + 1 \geq Ki−j+1≥K,即 j≤i−K+1j \leq i - K + 1j≤i−K+1
  • 答案 = pre[i - K + 1](前面所有合法的 c1c_1c1 数量)
推演验证
复制代码
输入: K=4, S="abababdb", c1='a', c2='b'

位置: 0  1  2  3  4  5  6  7
字符: a  b  a  b  a  b  d  b

pre数组(统计a的数量):
pre[0]=1 (a)
pre[1]=1
pre[2]=2 (a)
pre[3]=2
pre[4]=3 (a)
pre[5]=3
pre[6]=3
pre[7]=3

遍历每个b的位置:
i=1: b, i-K+1 = 1-4+1 = -2 < 0, 跳过
i=3: b, i-K+1 = 0, pre[0]=1, res+=1
i=5: b, i-K+1 = 2, pre[2]=2, res+=2
i=7: b, i-K+1 = 4, pre[4]=3, res+=3

res = 1 + 2 + 3 = 6 ✓
题解
python 复制代码
k = int(input())
s, c1, c2 = input().split()
pre = [0] * len(s)
res = 0

for i in range(len(s)):
    pre[i] = pre[i - 1] if i > 0 else 0  # 继承前一个值
    if c1 == s[i]:
        pre[i] += 1                       # 当前是c1,计数+1
    elif c2 == s[i] and i + 1 - k >= 0:   # 当前是c2,且长度足够
        res += pre[i - k + 1]             # 加上前面所有合法的c1数量

print(res)

复杂度 :时间 O(n)O(n)O(n),空间 O(n)O(n)O(n)

关键细节
坑点 说明
pre[i-1]继承 前缀和数组要继承前一个值,不是重新计数
条件判断 elif 确保不会同时满足c1和c2(一个位置不可能同时是两个字符)
边界条件 i + 1 - k >= 0 确保子串长度至少为K
索引处理 pre[i-k+1] 中的 i-k+1 可能为0,pre[0]已初始化

例题 3:帕鲁服务器 ⭐状态转移

项目 内容
类型 前缀和 + 区间查询
核心 统计区间 [l,r)[l, r)[l,r) 内"重启成功"的次数
题目描述

给定01字符串,111 表示端口关闭(会重启),000 表示正常运行。

重启成功定义 :服务从关闭状态(111)转为正常运行(000)。

即:位置 iii 为 111,且位置 i+1i+1i+1 为 000(最后一分钟为 111 则重启失败)。

求 mmm 次查询,每次查询区间 [l,r)[l, r)[l,r) 内重启成功的次数。

关键思路

状态转移

  • 如果 s[i] == '0' and s[i-1] == '1':位置 iii 是重启成功点
  • 用前缀和数组 qu[i] 记录到位置 iii 为止的重启成功次数

区间查询qu[r-1] - qu[l-1](注意区间是 [l,r)[l, r)[l,r))

推演验证
复制代码
输入: n=5, s="10110"

位置: 0  1  2  3  4
字符: 1  0  1  1  0

qu数组(重启成功次数前缀和):
qu[0]=0
i=1: s[1]=0, s[0]=1 → 重启成功!qu[1]=qu[0]+1=1
i=2: s[2]=1, s[1]=0 → 不是   qu[2]=qu[1]=1
i=3: s[3]=1, s[2]=1 → 不是   qu[3]=qu[2]=1
i=4: s[4]=0, s[3]=1 → 重启成功!qu[4]=qu[3]+1=2

qu = [0, 1, 1, 1, 2]

查询 [1, 2): l=1, r=2
  l = max(1-1, 0) = 0, r = min(5-1, 2-1) = 1
  qu[1] - qu[0] = 1 - 0 = 1 ✓

查询 [1, 3): l=1, r=3
  l = 0, r = min(4, 2) = 2
  qu[2] - qu[0] = 1 - 0 = 1 ✓

查询 [2, 4): l=2, r=4
  l = 1, r = min(4, 3) = 3
  qu[3] - qu[1] = 1 - 1 = 0 ✓

查询 [2, 5): l=2, r=5
  l = 1, r = min(4, 4) = 4
  qu[4] - qu[1] = 2 - 1 = 1 ✓
题解
python 复制代码
n = int(input())
s = list(input())
m = int(input())
qu = [0] * n

# 构建前缀和数组
for i in range(1, n):
    if int(s[i]) == 0 and int(s[i - 1]) == 1:
        qu[i] = 1 + qu[i - 1]   # 重启成功,计数+1
    else:
        qu[i] = qu[i - 1]       # 不继承

# 查询
for i in range(m):
    l, r = map(int, input().split())
    l = max(l - 1, 0)           # 转换为0-indexed
    r = min(n - 1, r - 1)       # 注意右边界
    print(qu[r] - qu[l])

复杂度 :时间 O(n+m)O(n + m)O(n+m),空间 O(n)O(n)O(n)

关键细节
坑点 说明
重启成功条件 s[i]==0 and s[i-1]==1,不是简单的01交替
最后一分钟为1 题目已说明视为重启失败,代码中自然处理(没有下一个0)
区间转换 输入是 [l,r)[l, r)[l,r),要转换为0-indexed并处理边界
qu[0]=0 第一个位置不可能重启成功(没有前一个状态)

例题 4:闪耀的灯光 ⭐二维前缀和

项目 内容
类型 二维前缀和 + 周期取模
核心 二维前缀和查询 + 亮度周期变化
题目描述

n×mn \times mn×m 的灯阵,每个灯亮度为 a[i][j]a[i][j]a[i][j]。

操作规则 :选择矩形区域,按下 kkk 次开关,每次亮度减1。

周期特性 :亮度最多减到 a[i][j]−ca[i][j] - ca[i][j]−c,再减则恢复为 a[i][j]a[i][j]a[i][j]。

即:亮度变化周期为 c+1c + 1c+1(从原亮度到 a[i][j]−ca[i][j]-ca[i][j]−c 再到原亮度)。

关键思路

周期取模

  • 每次按下开关,亮度变化为 −1-1−1(模 c+1c+1c+1 意义下)
  • 按下 kkk 次,等效于按下 kmod  (c+1)k \mod (c+1)kmod(c+1) 次
  • 每盏灯减少的亮度 = kmod  (c+1)k \mod (c+1)kmod(c+1)

二维前缀和

  • 先构建二维前缀和数组,O(1)O(1)O(1) 查询矩形区域和
  • 区域和 - 区域灯数 ×\times× 减少亮度 = 最终亮度
推演验证
复制代码
输入: n=3, m=3, c=3
a = [[14,14,17],[13,15,18],[13,16,19]]

操作1: (1,1,2,2,3)  左上角(1,1)到右下角(2,2),按3次
mod = c+1 = 4, k%mod = 3%4 = 3
区域和 = 14+14+13+15 = 56
灯数 = 4
减少量 = 4*3 = 12
结果 = 56 - 12 = 44 ✓

操作2: (2,2,3,3,5)
k%mod = 5%4 = 1
区域和 = 15+18+16+19 = 68
灯数 = 4
减少量 = 4*1 = 4
结果 = 68 - 4 = 64 ✓

操作3: (2,3,3,3,4)
k%mod = 4%4 = 0
区域和 = 18+19 = 37
灯数 = 2
减少量 = 2*0 = 0
结果 = 37 - 0 = 37 ✓
题解
python 复制代码
N = 310
a = [[0] * N for _ in range(N)]
s = [[0] * N for _ in range(N)]

n, m, c = map(int, input().split())
mod = c + 1

# 读入矩阵
for i in range(1, n + 1):
    a[i][1:m + 1] = list(map(int, input().split()))

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

# 查询
t = int(input())
for _ in range(t):
    x1, y1, x2, y2, k = map(int, input().split())
    # 二维前缀和查询
    res = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]
    amount = (x2 - x1 + 1) * (y2 - y1 + 1)  # 区域内灯的数量
    print(res - amount * (k % mod))

复杂度 :预处理 O(nm)O(nm)O(nm),每次查询 O(1)O(1)O(1),总时间 O(nm+t)O(nm + t)O(nm+t)

关键细节
坑点 说明
周期取模 mod = c + 1,不是c。因为亮度范围是 [a−c,a][a-c, a][a−c,a],共 c+1c+1c+1 个值
二维前缀和公式 s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]
查询公式 s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]
数组大小 N=310N=310N=310 略大于 300300300,留足余量
每次恢复初始值 题目说"下一次操作前恢复初始值",所以每次查询独立,不需要修改原数组

例题 5:重新排序 ⭐ 差分 + 贪心排序

项目 内容
类型 差分 + 贪心排序
核心 差分数组统计每个位置的查询次数,贪心排序最大化总和
题目描述

给定数组 AAA 和 mmm 个查询 [Li,Ri][L_i, R_i][Li,Ri],可以重新排列数组,使得所有查询结果的总和最大。

求:相比原数组,总和最多可以增加多少?

关键思路

第一步:差分数组统计每个位置的"查询覆盖次数"

对于每个查询 [L,R][L, R][L,R]:

  • diff[L] += 1
  • diff[R + 1] -= 1

还原后,diff[i] 表示位置 iii 被多少个查询覆盖。

第二步:贪心排序

  • 查询覆盖次数多的位置,应该放大的数(贡献多)
  • 查询覆盖次数少的位置,应该放小的数(贡献少)

贪心策略

  • diff 数组降序排序(查询覆盖次数)
  • nums 数组降序排序(数值大小)
  • 对应位置相乘:res += diff[i] * nums[i]

第三步:计算增加量

增加量 = 重新排序后的总和 - 原数组的总和

推演验证
复制代码
输入: n=5, nums=[1,2,3,4,5], m=2
查询: [1,3], [2,5]

原数组总和:
查询1: 1+2+3 = 6
查询2: 2+3+4+5 = 14
总和 = 20

差分数组统计覆盖次数:
diff = [0, 0, 0, 0, 0, 0]
查询[1,3]: diff[1]+=1, diff[4]-=1 → diff=[0,1,0,0,-1,0]
查询[2,5]: diff[2]+=1, diff[6]-=1 → diff=[0,1,1,0,-1,0,-1]

还原覆盖次数:
cnt[1]=1, cnt[2]=2, cnt[3]=2, cnt[4]=1, cnt[5]=1
diff数组(有效部分)= [1, 2, 2, 1, 1]

贪心排序:
nums降序: [5, 4, 3, 2, 1]
diff降序: [2, 2, 1, 1, 1]

重新排序后总和:
5*2 + 4*2 + 3*1 + 2*1 + 1*1 = 10 + 8 + 3 + 2 + 1 = 24

增加量 = 24 - 20 = 4 ✓

最优排列:(1, 4, 5, 2, 3) 或类似
查询1: 1+4+5 = 10
查询2: 4+5+2+3 = 14
总和 = 24 ✓
题解
python 复制代码
n = int(input())
nums = [0] + list(map(int, input().split()))  # 1-indexed

# 原数组前缀和
pres = [0] * (n + 1)
for i in range(1, n + 1):
    pres[i] = pres[i - 1] + nums[i]

m = int(input())
s = 0                    # 原数组查询总和
diff = [0] * (n + 2)     # 差分数组

for _ in range(m):
    l, r = map(int, input().split())
    s += pres[r] - pres[l - 1]   # 原数组查询和
    diff[l] += 1                 # 差分:左端点+1
    diff[r + 1] -= 1             # 差分:右端点+1

# 还原差分数组,得到每个位置的覆盖次数
for i in range(1, n + 1):
    diff[i] += diff[i - 1]

# 贪心排序
nums.sort(reverse=True)          # 数值降序
diff.sort(reverse=True)          # 覆盖次数降序

# 计算重新排序后的最大总和
res = 0
for i in range(n):
    res += diff[i] * nums[i]

print(res - s)                    # 增加量

复杂度 :时间 O(nlog⁡n+m)O(n \log n + m)O(nlogn+m),空间 O(n)O(n)O(n)

关键细节
坑点 说明
差分数组 diff[l] += 1, diff[r+1] -= 1,注意右边界是 r+1
还原差分 diff[i] += diff[i-1],得到每个位置的实际覆盖次数
贪心正确性 排序不等式:大的配大的,总和最大
1-indexed 数组从1开始,方便处理前缀和
答案格式 输出的是"增加量",不是"最大总和"

三、今日刷题总结

前缀和与差分解题模板

python 复制代码
# 模板1:一维前缀和
pre = [0] * (n + 1)
for i in range(1, n + 1):
    pre[i] = pre[i - 1] + a[i]
# 查询[l,r]: pre[r] - pre[l-1]

# 模板2:一维差分
diff = [0] * (n + 2)
diff[l] += x
diff[r + 1] -= x
for i in range(1, n + 1):
    a[i] = a[i - 1] + diff[i]

# 模板3:二维前缀和
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]
# 查询矩形[x1,y1]到[x2,y2]:
# s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]

# 模板4:差分统计+贪心排序
diff = [0] * (n + 2)
for each query [l,r]:
    diff[l] += 1
    diff[r+1] -= 1
for i in range(1, n+1):
    diff[i] += diff[i-1]
# diff[i]表示位置i被覆盖的次数
# 贪心:大的数放覆盖次数多的位置

四、结语

前缀和与差分是蓝桥杯国赛的必备武器,它们能将复杂问题化繁为简:

🌟 今日收获

  1. 前缀和 :区间查询从 O(n)O(n)O(n) 降到 O(1)O(1)O(1)
  2. 差分 :区间修改从 O(n)O(n)O(n) 降到 O(1)O(1)O(1)
  3. 二维前缀和:矩形区域查询利器
  4. 差分+贪心:统计贡献后排序,最大化总和
  5. 周期问题:取模处理循环变化

记住:看到区间查询想前缀和,看到区间修改想差分,看到二维矩阵想二维前缀和

继续加油,国赛见!💪


如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐ + 关注 🔔,你的支持是我持续更新的动力!

相关推荐
m0_609160491 小时前
Golang项目目录结构如何组织_Golang项目结构教程【核心】
jvm·数据库·python
Dust-Chasing1 小时前
Claude Code源码剖析 - Phase1
人工智能·python·ai
m0_463672201 小时前
如何优雅处理SQL存储过程异常_使用TRY-CATCH块机制
jvm·数据库·python
li星野1 小时前
动态规划十题通关:从爬楼梯到编辑距离(Python + C++)
c++·python·学习·算法·动态规划
ㄟ留恋さ寂寞1 小时前
HTML5中SharedWorker生命周期与浏览器进程关闭的关系
jvm·数据库·python
彳亍1011 小时前
MongoDB备节点无法读取数据怎么解决_rs.slaveOk()与Secondary读取权限
jvm·数据库·python
m0_690825821 小时前
CSS如何实现圆形头像裁剪_使用border-radius50属性
jvm·数据库·python
老纪1 小时前
HTML函数工具在NAS设备上能运行吗_轻服务器适配指南【指南】
jvm·数据库·python
老纪1 小时前
SQL如何高效提取大表前几行:分页查询与OFFSET优化
jvm·数据库·python