备战蓝桥杯国赛【Day 15】

📌 写在前面 :今天的4道题全部来自第十四届蓝桥杯省赛真题 ,难度覆盖 ⭐⭐ 到 ⭐⭐⭐⭐⭐,是检验省赛备战成果的绝佳素材。核心考点包括:前缀和优化枚举、单调队列维护区间最值、二维差分处理矩阵变换、按位独立统计 。每道题我都会从"暴力为何超时"讲起,一步步推导到最优解,确保你不仅懂代码,更懂思维路径


📚 今日刷题清单

题号 题目 来源 类型 难度 核心考点
1 子串简写 蓝桥杯3514 前缀和 ⭐⭐⭐ 贡献法思想、前缀和优化
2 最大子矩阵 蓝桥杯2147 单调队列+枚举 ⭐⭐⭐⭐ 二维转一维、滑动窗口最值
3 闪耀的灯光 蓝桥杯3811 二维前缀和 ⭐⭐⭐⭐ 矩阵变换、差分思想
4 异或和之和 蓝桥杯3507 位运算+组合数学 ⭐⭐⭐⭐⭐ 按位独立、贡献法、O(n)优化

一、子串简写 ⭐⭐⭐

1.1 题目描述

程序猿圈子里流行一种简写:只保留首尾字符,中间用长度代替(如 internationalizationi18n)。

给定字符串 S、整数 K、字符 c1c2。求有多少个以 c1 开头、c2 结尾、长度 ≥ K 的子串可以采用这种简写。

输入KS c1 c2(空格分隔)
输出:满足条件的子串个数

1.2 关键思路:贡献法 + 前缀和

暴力思路(O(n²)) :枚举所有子串,检查首尾字符和长度。
超时原因|S| ≤ 5×10⁵,子串数量是 O(n²) 级别。

优化思路------贡献法

  • 对于每个位置 i 上的 c2,它能和前面多少个 c1 组成合法子串?
  • 条件:i - j + 1 ≥ Kj ≤ i - K + 1
  • 即:位置 ic2 的贡献 = 区间 [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 可能到几百,四重循环无法接受。

优化思路

  1. 枚举上下边界 :固定子矩阵的上边 top 和下边 bottom,将问题压缩成一维
  2. 列最值数组 :对于每一列 j,计算 col_min[j] = min(a[top..bottom][j])col_max[j] = max(a[top..bottom][j])
  3. 一维问题转化 :现在问题变成------在数组 col_mincol_max 上,找最长的连续子数组,使得 max(col_max) - min(col_min) ≤ limit
  4. 滑动窗口+单调队列 :用双指针维护窗口,窗口内需要快速知道最大值和最小值 → 单调队列

单调队列作用

  • 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 初始 infcol_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]-cc+1 个值之间循环

进行 t 次操作,每次选择一个子矩阵,将该区域内每盏灯按 k 次开关。每次操作前所有灯恢复初始值 。求每次操作后,该子矩阵内所有灯的总亮度

数据范围n,m ≤ 300t ≤ 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] ≥ 10c ≤ 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]、位运算优先级 ⭐⭐⭐⭐⭐

📌 记得 点赞收藏,算法路上不迷路!有问题评论区见 👇

相关推荐
彳亍1011 小时前
如何用 Dask 替代 Pandas 实现高效 Excel 数据处理
jvm·数据库·python
2301_783848651 小时前
c++怎么把多个变量一次性写入二进制文件_结构体对齐与write【实战】
jvm·数据库·python
码界筑梦坊1 小时前
123-基于Python的特斯拉超级充电站分布数据可视化分析系统
开发语言·python·信息可视化·数据分析·毕业设计·echarts·fastapi
像素猎人1 小时前
蓝桥杯OJ505数字三角形【与简单数字三角形不一样】【2020年省赛真题】【动态规划】
蓝桥杯·动态规划
wang3zc1 小时前
如何在 WooCommerce 后台按订单总金额精准筛选订单
jvm·数据库·python
AIGC包拥它2 小时前
RAG 项目实战进阶:基于 FastAPI + Vue3 前后端架构全面重构 LangChain 0.3 集成 Milvus 2.5 构建大模型智能应用
人工智能·python·重构·vue·fastapi·milvus·ai-native
闲人编程2 小时前
Agent的评估体系(AgentEval):如何判断一个Agent好坏?
大数据·人工智能·python·算法·agent·智能体·swe
m0_702036533 小时前
html标签如何提升可访问性_aria-label与title区别【指南】
jvm·数据库·python
BU摆烂会噶3 小时前
【LangGraph】节点内调用与状态隔离
android·人工智能·python·ui·langchain·人机交互