📚 今日刷题清单
| 题号 | 题目 | 类型 | 难度 | 核心考点 |
|---|---|---|---|---|
| 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] += 1diff[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(nlogn+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被覆盖的次数
# 贪心:大的数放覆盖次数多的位置
四、结语
前缀和与差分是蓝桥杯国赛的必备武器,它们能将复杂问题化繁为简:
🌟 今日收获:
- 前缀和 :区间查询从 O(n)O(n)O(n) 降到 O(1)O(1)O(1)
- 差分 :区间修改从 O(n)O(n)O(n) 降到 O(1)O(1)O(1)
- 二维前缀和:矩形区域查询利器
- 差分+贪心:统计贡献后排序,最大化总和
- 周期问题:取模处理循环变化
记住:看到区间查询想前缀和,看到区间修改想差分,看到二维矩阵想二维前缀和!
继续加油,国赛见!💪
如果本文对你有帮助,欢迎 点赞 👍 + 收藏 ⭐ + 关注 🔔,你的支持是我持续更新的动力!