📌 写在前面 :今天的4道题全部来自第十四届蓝桥杯省赛真题 ,难度覆盖 ⭐⭐ 到 ⭐⭐⭐⭐⭐,是检验省赛备战成果的绝佳素材。核心考点包括:前缀和优化枚举、单调队列维护区间最值、二维差分处理矩阵变换、按位独立统计 。每道题我都会从"暴力为何超时"讲起,一步步推导到最优解,确保你不仅懂代码,更懂思维路径。
📚 今日刷题清单
| 题号 | 题目 | 来源 | 类型 | 难度 | 核心考点 |
|---|---|---|---|---|---|
| 1 | 子串简写 | 蓝桥杯3514 | 前缀和 | ⭐⭐⭐ | 贡献法思想、前缀和优化 |
| 2 | 最大子矩阵 | 蓝桥杯2147 | 单调队列+枚举 | ⭐⭐⭐⭐ | 二维转一维、滑动窗口最值 |
| 3 | 闪耀的灯光 | 蓝桥杯3811 | 二维前缀和 | ⭐⭐⭐⭐ | 矩阵变换、差分思想 |
| 4 | 异或和之和 | 蓝桥杯3507 | 位运算+组合数学 | ⭐⭐⭐⭐⭐ | 按位独立、贡献法、O(n)优化 |
一、子串简写 ⭐⭐⭐
1.1 题目描述
程序猿圈子里流行一种简写:只保留首尾字符,中间用长度代替(如 internationalization → i18n)。
给定字符串 S、整数 K、字符 c1 和 c2。求有多少个以 c1 开头、c2 结尾、长度 ≥ K 的子串可以采用这种简写。
输入 :K,S c1 c2(空格分隔)
输出:满足条件的子串个数
1.2 关键思路:贡献法 + 前缀和
暴力思路(O(n²)) :枚举所有子串,检查首尾字符和长度。
超时原因 :|S| ≤ 5×10⁵,子串数量是 O(n²) 级别。
优化思路------贡献法:
- 对于每个位置
i上的c2,它能和前面多少个c1组成合法子串? - 条件:
i - j + 1 ≥ K→j ≤ i - K + 1 - 即:位置
i的c2的贡献 = 区间[0, i-K+1]内c1的出现次数
前缀和维护 :用 pre[i] 表示前 i 个字符中 c1 的出现次数,O(1) 查询区间和。
1.3 推演验证
输入: 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数组(c1='a'的个数):
pre[0]=1 (a)
pre[1]=1 (b不是a)
pre[2]=2 (a)
pre[3]=2 (b)
pre[4]=3 (a)
pre[5]=3 (b)
pre[6]=3 (d)
pre[7]=3 (b)
遍历每个c2的位置:
i=1 (b): i-K+1 = 1-4+1 = -2 → max(0,-2)=0, 贡献=pre[0]=1 ✓ 子串[0,1]="ab" 长度2<<4?
等等,这里要注意:长度≥K,即i-j+1≥K → j≤i-K+1
重新计算:
i=1: j≤1-4+1=-2,没有合法j,贡献0
i=3: j≤3-4+1=0,pre[0]=1,贡献1 → "abab" [0,3]
i=5: j≤5-4+1=2,pre[2]=2,贡献2 → "ababab"[0,5], "babab"[1,5]? 不对,c1必须在开头
应该是j=0("ababab"), j=2("babab"? 开头是b不是a)
等等,pre[2]=2表示位置0和2都是a,所以:
j=0: [0,5]="ababab" 长度6≥4 ✓
j=2: [2,5]="abab" 长度4≥4 ✓
贡献确实是2
i=7: j≤7-4+1=4,pre[4]=3,贡献3
j=0: [0,7]="abababdb" 长度8 ✓
j=2: [2,7]="ababdb" 长度6 ✓
j=4: [4,7]="abdb" 长度4 ✓
总贡献 = 0+1+2+3 = 6 ✓
1.4 题解
python
import sys
input = sys.stdin.readline
k = int(input())
s, a, b = input().split()
n = len(s)
# pre[i] = 前i个字符中,字符a出现的次数(前缀和)
pre = [0] * n
ans = 0
for i in range(n):
# 继承前一个值
pre[i] = pre[i - 1] if i > 0 else 0
# 当前是c1,计数+1
if s[i] == a:
pre[i] += 1
# 当前是c2,计算贡献
# 需要找开头c1的位置j,满足 i-j+1 >= k → j <= i-k+1
elif s[i] == b and i - k + 1 >= 0:
# pre[i-k+1] 就是区间[0, i-k+1]内c1的个数
ans += pre[i - k + 1]
print(ans)
复杂度 :时间 O(n),空间 O(n)(可优化至 O(1))
1.5 关键细节
| 坑点 | 说明 |
|---|---|
| 下标越界 | i-k+1 可能为负数,必须判断 i-k+1 >= 0 |
| pre[i-1] 越界 | i=0 时不能访问 pre[-1](Python中-1是最后一个元素!) |
| 字符读取 | input().split() 读取,注意字符串中可能有空格?不,题目说只含小写字母 |
| 答案类型 | 答案可能很大,要用 long long(Python自动处理) |
二、最大子矩阵 ⭐⭐⭐⭐
2.1 题目描述
给定 n×m 矩阵,定义子矩阵的稳定度 = 子矩阵中最大值 - 最小值。求稳定度 ≤ limit 的所有子矩阵中,面积最大 是多少。
2.2 关键思路:二维转一维 + 滑动窗口
暴力思路(O(n²m²)) :枚举上下左右四个边界,再遍历求最值。
超时原因 :n,m 可能到几百,四重循环无法接受。
优化思路:
- 枚举上下边界 :固定子矩阵的上边
top和下边bottom,将问题压缩成一维 - 列最值数组 :对于每一列
j,计算col_min[j] = min(a[top..bottom][j]),col_max[j] = max(a[top..bottom][j]) - 一维问题转化 :现在问题变成------在数组
col_min和col_max上,找最长的连续子数组,使得max(col_max) - min(col_min) ≤ limit - 滑动窗口+单调队列 :用双指针维护窗口,窗口内需要快速知道最大值和最小值 → 单调队列!
单调队列作用:
maxx队列:维护窗口内最大值(队首是最大值)minn队列:维护窗口内最小值(队首是最小值)- 窗口右移时加入新元素,左移时弹出过期元素,均摊 O(1)
2.3 推演验证
输入: n=2, m=3, limit=1
矩阵:
1 2 3
2 3 4
枚举 top=0, bottom=0(第一行):
col_min = [1, 2, 3], col_max = [1, 2, 3]
滑动窗口:
right=0: [1], max=1,min=1,diff=0≤1, 面积=1×1=1
right=1: [1,2], max=2,min=1,diff=1≤1, 面积=1×2=2
right=2: [1,2,3], max=3,min=1,diff=2>1, 收缩left
left=0弹出1: [2,3], diff=1≤1, 面积=1×2=2
left=1弹出2: [3], diff=0≤1, 面积=1×1=1
枚举 top=0, bottom=1(两行合并):
col_min = [1, 2, 3], col_max = [2, 3, 4]
right=0: [1], max=2,min=1,diff=1≤1, 面积=2×1=2
right=1: [1,2], max=3,min=1,diff=2>1, 收缩
left=0: [2], max=3,min=2,diff=1≤1, 面积=2×1=2
再收缩? diff已经≤1了
等等,窗口是[0,1],left=0时弹出1,窗口变[1],即只有2
max=3,min=2,diff=1, 面积=2×1=2
right=2: [2,3], max=4,min=2,diff=2>1, 收缩
left=1弹出2: [3], max=4,min=3,diff=1, 面积=2×1=2
最大面积 = 2
2.4 题解
python
from collections import deque
n, m = map(int, input().split())
a = []
for _ in range(n):
row = list(map(int, input().split()))
a.append(row)
limit = int(input())
ans = 0
# 枚举上下边界
for top in range(n):
# 初始化每列的最小值和最大值
col_min = [float('inf')] * m
col_max = [float('-inf')] * m
for bottom in range(top, n):
# 更新每列的最值(随着bottom下移)
for j in range(m):
col_min[j] = min(col_min[j], a[bottom][j])
col_max[j] = max(col_max[j], a[bottom][j])
# 现在问题变成:在col_min和col_max上找最长子数组
# 使得 max(col_max[l..r]) - min(col_min[l..r]) <= limit
# 滑动窗口 + 单调队列
left = 0
min_q = deque() # 维护最小值,存 (值, 索引)
max_q = deque() # 维护最大值,存 (值, 索引)
for right in range(m):
# 维护最小值队列(单调递增)
while min_q and min_q[-1][0] >= col_min[right]:
min_q.pop()
min_q.append((col_min[right], right))
# 维护最大值队列(单调递减)
while max_q and max_q[-1][0] <= col_max[right]:
max_q.pop()
max_q.append((col_max[right], right))
# 收缩左边界,直到满足条件
while left <= right:
# 清理过期的队首元素
while min_q and min_q[0][1] < left:
min_q.popleft()
while max_q and max_q[0][1] < left:
max_q.popleft()
current_diff = max_q[0][0] - min_q[0][0]
if current_diff <= limit:
break # 满足条件,停止收缩
# 不满足,收缩左边界
left += 1
# 计算当前合法窗口的面积
# 高度 = bottom - top + 1, 宽度 = right - left + 1
area = (bottom - top + 1) * (right - left + 1)
ans = max(ans, area)
print(ans)
复杂度 :时间 O(n² × m),空间 O(m)
2.5 关键细节
| 坑点 | 说明 |
|---|---|
| 单调队列实现 | Python用 deque,队首弹出过期元素,队尾维护单调性 |
| 过期元素清理 | 左边界右移时,要弹出索引 < left 的元素 |
| 窗口收缩逻辑 | 先加入 right,再收缩 left 直到满足条件 |
| 面积计算时机 | 每次 right 扩展后都要计算,因为窗口可能已经合法 |
| 初始值设置 | col_min 初始 inf,col_max 初始 -inf |
感谢提供完整题目!现在我来更新"闪耀的灯光"的题解。这道题的核心是每次操作独立,基于初始状态计算,而不是累积修改。让我重新分析:
三、闪耀的灯光 ⭐⭐⭐⭐
3.1 题目描述
蓝桥公园有 n×m 盏灯,初始亮度为 a[i][j]。每盏灯有一个开关:
- 按一次:亮度
-1 - 亮度最低降到
a[i][j] - c - 如果当前亮度已经是
a[i][j] - c,再按一次会恢复 为a[i][j]
即:每盏灯的亮度在 a[i][j], a[i][j]-1, ..., a[i][j]-c 这 c+1 个值之间循环。
进行 t 次操作,每次选择一个子矩阵,将该区域内每盏灯按 k 次开关。每次操作前所有灯恢复初始值 。求每次操作后,该子矩阵内所有灯的总亮度。
数据范围 :n,m ≤ 300,t ≤ 10⁵,k ≤ 10⁹
3.2 关键思路:周期取模 + 二维前缀和
核心观察:
- 每盏灯的亮度变化周期为
c+1(按c+1次回到原值) - 按
k次等价于按k % (c+1)次 - 设
k' = k % (c+1),则每盏灯亮度变化为-k'(但如果初始亮度不够减,会循环)
等等,重新理解"循环":
- 灯的状态序列:
a[i][j] → a[i][j]-1 → ... → a[i][j]-c → a[i][j] → ... - 按
k次后,亮度 =a[i][j] - (k % (c+1)),但如果结果为负数则加c+1? - 不对,题目说"数据保证每盏灯的亮度不会减少至负数",且
a[i][j] ≥ 10,c ≤ 10,所以a[i][j]-c ≥ 0
更准确地 :按 k 次后,亮度 = a[i][j] - k',其中 k' = k % (c+1),且 a[i][j] - k' ≥ a[i][j] - c(因为 k' ≤ c)。
所以:每盏灯按 k 次后,亮度 = a[i][j] - k % (c+1)
子矩阵总亮度:
- 设
k' = k % (c+1),num = (x2-x1+1) × (y2-y1+1)为子矩阵格子数 - 总亮度 =
Σ(a[i][j] - k')=Σa[i][j] - num × k' Σa[i][j]用二维前缀和 O(1) 求!
3.3 推演验证
输入: n=3, m=3, c=3
矩阵:
14 14 17
13 15 18
13 16 19
t=3
操作1: x1=1,y1=1,x2=2,y2=2, k=3
k' = 3 % 4 = 3
num = 2×2 = 4
子矩阵和 = 14+14+13+15 = 56
总亮度 = 56 - 4×3 = 56 - 12 = 44 ✓
操作2: x1=2,y1=2,x2=3,y2=3, k=5
k' = 5 % 4 = 1
num = 2×2 = 4
子矩阵和 = 15+18+16+19 = 68
总亮度 = 68 - 4×1 = 68 - 4 = 64 ✓
操作3: x1=2,y1=3,x2=3,y2=3, k=4
k' = 4 % 4 = 0
num = 2×1 = 2
子矩阵和 = 18+19 = 37
总亮度 = 37 - 2×0 = 37 ✓
3.4 题解
python
import sys
input = sys.stdin.readline
n, m, c = map(int, input().split())
# 构建矩阵,下标从1开始方便前缀和
a = [[0] * (m + 1)]
for _ in range(n):
row = [0] + list(map(int, input().split()))
a.append(row)
# 二维前缀和: pre_sum[i][j] = 矩形(1,1)到(i,j)的元素和
pre_sum = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
pre_sum[i][j] = (pre_sum[i-1][j] + pre_sum[i][j-1]
- pre_sum[i-1][j-1] + a[i][j])
t = int(input())
for _ in range(t):
x1, y1, x2, y2, k = map(int, input().split())
# k对周期(c+1)取模,因为按c+1次等于没按
k = k % (c + 1)
# 子矩阵格子数
num = (y2 - y1 + 1) * (x2 - x1 + 1)
# 用二维前缀和求子矩阵原始亮度之和
# 公式: sum(x1,y1到x2,y2) = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1]
sub_sum = (pre_sum[x2][y2] - pre_sum[x1-1][y2]
- pre_sum[x2][y1-1] + pre_sum[x1-1][y1-1])
# 每盏灯减少k,总共减少 num*k
ans = sub_sum - num * k
print(ans)
复杂度:
- 预处理 :
O(n × m) - 每次查询 :
O(1) - 总复杂度 :
O(n × m + t)
3.5 关键细节
| 坑点 | 说明 |
|---|---|
k % (c+1) |
周期是 c+1,不是 c!按 c+1 次回到原值 |
| 下标从1开始 | 输入坐标就是1-indexed,直接用,不用转换 |
| 二维前缀和公式 | pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1],符号别搞错 |
| "恢复初始值" | 每次操作独立,不用维护修改状态,直接用初始矩阵的前缀和 |
t 很大 |
t ≤ 10⁵,必须 O(1) 查询,预处理前缀和是关键 |
四、异或和之和 ⭐⭐⭐⭐⭐ 按位独立+贡献法
4.1 题目描述
给定数组 a,求所有子数组的异或和的总和。
即:对每对 1 ≤ L ≤ R ≤ n,求 a[L] ^ a[L+1] ^ ... ^ a[R],把所有结果加起来。
4.2 关键思路:按位独立+贡献法
暴力思路(O(n²)) :枚举所有子数组,计算异或和,累加。
超时原因 :n ≤ 10⁵,子数组数量 O(n²),需要 10¹⁰ 次操作。
优化思路------按位独立:
- 异或运算按位独立 ,第
k位的结果只和第k位有关 - 对于第
k位,如果子数组异或结果的第k位是1,则贡献2^k到答案 - 问题转化:有多少个子数组,其异或结果的第 k 位是 1?
前缀异或:
pre_xor[i] = a[0] ^ a[1] ^ ... ^ a[i]- 子数组
[L,R]的异或 =pre_xor[R] ^ pre_xor[L-1]
第 k 位为 1 的条件:
pre_xor[R]和pre_xor[L-1]的第k位不同 (因为0^1=1,1^0=1,0^0=0,1^1=0)
统计方法:
- 遍历所有
pre_xor值,统计第k位为0的个数cnt0,为1的个数cnt1 - 子数组数量 =
cnt0 × cnt1(选一个0和一个1配对) - 注意:
pre_xor数组有n+1个元素(包含pre_xor[-1]=0)
4.3 推演验证
输入: n=3, a=[1, 2, 3]
前缀异或:
pre[0] = 0 (空前缀)
pre[1] = 1 = 01
pre[2] = 1^2 = 3 = 11
pre[3] = 3^3 = 0 = 00
所有子数组异或:
[1]: 1 = 01
[2]: 2 = 10
[3]: 3 = 11
[1,2]: 1^2 = 3 = 11
[2,3]: 2^3 = 1 = 01
[1,2,3]: 1^2^3 = 0 = 00
总和 = 1+2+3+3+1+0 = 10
按位计算:
第0位(1):
pre中第0位: 0(0), 1(1), 3(1), 0(0) → cnt0=2, cnt1=2
贡献 = 2×2×1 = 4
第1位(2):
pre中第1位: 0(0), 1(0), 3(1), 0(0) → cnt0=3, cnt1=1
贡献 = 3×1×2 = 6
总和 = 4+6 = 10 ✓
验证子数组第0位为1的个数:
[1]:1 ✓, [2]:0, [3]:1 ✓, [1,2]:1 ✓, [2,3]:1 ✓, [1,2,3]:0
共4个,cnt0×cnt1=4 ✓
第1位为1的个数:
[1]:0, [2]:1 ✓, [3]:1 ✓, [1,2]:1 ✓, [2,3]:0, [1,2,3]:0
共3个,cnt0×cnt1=3? 但算出来是3×1=3,贡献3×2=6,而实际第1位为1的子数组和是 2+3+3=8?
等等,重新算:
第1位为1的子数组:[2]=2, [3]=2, [1,2]=2(第1位是1,值是2)
贡献 = 2+2+2 = 6? 不对,[2]=2(10), [3]=3(11), [1,2]=3(11)
第1位为1的值:2, 2, 2 → 总和6?但2+3+3=8
哦,我搞混了。按位独立的意思是:
第1位为1,则该子数组对总和的贡献包含 2^1=2。
有多少个子数组第1位为1?3个。所以贡献 3×2=6。
实际子数组值:2,3,3,它们的第1位都是1,所以每个都贡献2到总和(作为第1位的部分)。
总和的第1位部分 = 2+2+2 = 6 ✓
加上第0位部分:4个子数组第0位为1,每个贡献1,共4。
总和 = 6+4 = 10 ✓
4.4 题解
python
n = int(input())
a = list(map(int, input().split()))
# 前缀异或数组,pre_xor[i] = a[0]^a[1]^...^a[i-1]
# pre_xor[0] = 0 表示空前缀
pre_xor = [0] * (n + 1)
for i in range(n):
pre_xor[i + 1] = pre_xor[i] ^ a[i]
# 最多21位(因为 a[i] ≤ 2^20)
MAX_BIT = 21
ans = 0
for k in range(MAX_BIT):
cnt0 = 0 # 第k位为0的前缀异或个数
cnt1 = 0 # 第k位为1的前缀异或个数
for num in pre_xor:
if (num >> k) & 1:
cnt1 += 1
else:
cnt0 += 1
# 第k位的总贡献 = 配对数 × 该位的权值
# 0和1配对,异或结果为1
ans += cnt0 * cnt1 * (1 << k)
print(ans)
复杂度 :时间 O(21 × n),空间 O(n)(可优化至 O(1))
4.5 关键细节
| 坑点 | 说明 |
|---|---|
| 前缀异或数组大小 | n+1 个元素,pre_xor[0]=0 对应空前缀,容易漏掉! |
| 位运算优先级 | (num >> k) & 1 的括号不能省略 |
| 21位足够 | 根据数据范围 a[i] ≤ 2^20,21位覆盖所有可能 |
| cnt0 + cnt1 = n+1 | 可以用这个验证计数是否正确 |
| long long | 答案可能很大,Python自动处理,C++要开long long |
4.6 进一步优化
空间优化:不需要存 pre_xor 数组,可以边读边处理:
python
n = int(input())
a = list(map(int, input().split()))
MAX_BIT = 21
ans = 0
for k in range(MAX_BIT):
cnt0, cnt1 = 1, 0 # pre_xor[0]=0,第0位是0,所以cnt0初始为1
xor_sum = 0
for num in a:
xor_sum ^= num
if (xor_sum >> k) & 1:
cnt1 += 1
else:
cnt0 += 1
ans += cnt0 * cnt1 * (1 << k)
print(ans)
这样空间复杂度降到 O(1)(除了输入数组)。
🎯 今日复盘总结
| 题目 | 核心技巧 | 思维路径 | 易错点 | 国赛价值 |
|---|---|---|---|---|
| 子串简写 | 贡献法+前缀和 | 枚举右端点,统计左端点合法个数 | 下标越界、pre[-1] | ⭐⭐⭐ |
| 最大子矩阵 | 二维转一维+单调队列 | 枚举上下边界→滑动窗口→维护最值 | 单调队列实现、过期元素 | ⭐⭐⭐⭐ |
| 闪耀的灯光 | 二维前缀和 | 子矩阵和→容斥公式 | 下标从1开始、公式符号 | ⭐⭐⭐⭐ |
| 异或和之和 | 按位独立+贡献法 | 异或按位独立→0/1配对统计 | 空前缀pre[0]、位运算优先级 | ⭐⭐⭐⭐⭐ |
📌 记得 点赞收藏,算法路上不迷路!有问题评论区见 👇