备战蓝桥杯国赛【Day 6】

例题 1:中位数(蓝桥杯 P2143)

项目 内容
链接 https://www.lanqiao.cn/problems/2143/learning/
类型 二分答案 + 数学分析
核心 中位数性质、二分找分界点

题目描述

给定长度为 n 的数组 arr,定义操作:每次选择一个数 x,将数组中所有 < x 的数变为 x。求使数组中位数(排序后中间位置的数)最大的最小操作次数,并输出每个元素的最终值。

输入格式

复制代码
n
arr[1] arr[2] ... arr[n]

输出格式

n 个整数,表示每个元素的最终值。

题解

关键观察 :最终数组是非递减的(因为每次操作把小的变大)。设最终中位数为 ans,则需要:

  • 大于 ans 的元素个数 <= 小于 ans 的元素个数

二分找 anscheck(x) 判断能否使中位数 >= x

python 复制代码
import bisect

n = int(input())
arr = list(map(int, input().split()))
a = sorted(arr)

def check(x):
    # 大于x的元素个数 <= 小于x的元素个数
    greater = n - bisect.bisect_right(a, x)  # 严格大于x的个数
    lesser = bisect.bisect_left(a, x)         # 严格小于x的个数
    return greater <= lesser

left, right = 0, 100000
ans = 0
while left <= right:
    mid = (left + right) // 2
    if check(mid):
        ans = mid
        right = mid - 1  # 尝试更小的(让中位数更"容易"达到)
    else:
        left = mid + 1

# 判断ans是否恰好是中位数
greater = n - bisect.bisect_right(a, ans)
lesser = bisect.bisect_left(a, ans)
t = 1 if greater == lesser else 0  # 是否需要调整

res = []
for i in arr:
    if i >= ans:
        res.append(0)          # 不需要操作
    else:
        res.append(ans + t - i) # 需要操作到 ans+t

print(' '.join(map(str, res)))

推演验证

复制代码
输入:n=5, arr=[1,2,3,4,5]

排序后:[1,2,3,4,5]
check(3): greater=5-3=2 (4,5), lesser=2 (1,2), 2<=2 True
check(4): greater=5-4=1 (5), lesser=3 (1,2,3), 1<=3 True
check(5): greater=0, lesser=4, 0<=4 True

但我们要找的是"使中位数最大的最小操作次数",所以需要重新理解题意...

实际上这题是:找最大的中位数,使得操作次数最少。
如果 ans=3, t=0: 1→3(2次), 2→3(1次), 3,4,5不变,总操作3次
如果 ans=4, t=1: 需要中位数为4,但greater=1,lesser=3不相等

最终输出每个元素变成的值,而非操作次数。

关键细节

坑点 说明
bisect_left vs bisect_right 左边界找 <,右边界找 <=
t 的调整 greater == lesser 时需要特殊处理
输出的是最终值 不是操作次数,是 ans+t

例题 2:阶乘末尾零的个数(蓝桥杯 P2145)

项目 内容
链接 https://www.lanqiao.cn/problems/2145/learning/
类型 二分答案 + 数论
核心 勒让德公式、二分找第K个

题目描述

求最小的正整数 n,使得 n! 末尾恰好有 k 个零。如果不存在,输出 -1

输入格式

复制代码
k

输出格式

一个整数,n-1

题解

数学知识n! 末尾零的个数 = n! 中因子5的个数(因为2比5多)。

勒让德公式count = n//5 + n//25 + n//25 + ...

二分找 ncheck(n) 计算 n! 末尾零的个数。

python 复制代码
k = int(input())

def check(x):
    """计算x!末尾有多少个零"""
    cnt = 0
    while x >= 5:
        cnt += x // 5
        x //= 5
    return cnt

left, right = 1, 10**19  # 范围足够大
ans = 0

while left <= right:
    mid = (left + right) // 2
    if check(mid) >= k:
        ans = mid
        right = mid - 1  # 尝试更小的n
    else:
        left = mid + 1   # 零不够,需要更大的n

# 验证:必须恰好等于k,不能多
if check(ans) == k:
    print(ans)
else:
    print(-1)

推演验证

复制代码
k=1:
check(4)=0, check(5)=1, check(6)=1...
二分找到ans=5, check(5)=1==1 → 输出5

k=2:
check(9)=1, check(10)=2, check(11)=2...
ans=10, check(10)=2==2 → 输出10

k=5:
check(24)=4, check(25)=6...
不存在恰好5个零的阶乘!
check(ans)=check(25)=6 != 5 → 输出-1

关键细节

坑点 说明
范围要够大 10**19 防止溢出,Python无压力
最后必须验证 check(ans) == k,因为可能存在"跳跃"
不存在的情况 当k=5时,24!有4个零,25!有6个零,没有5个零的

例题 3:最大子矩阵(蓝桥杯 P2147)

项目 内容
链接 https://www.lanqiao.cn/problems/2147/learning/
类型 二分答案 + 滑动窗口 / 双指针
核心 枚举上下边界 + 滑动窗口找最宽列

题目描述

给定 n×m 矩阵,求满足"最大值-最小值 <= limit"的最大子矩阵面积。

输入格式

复制代码
n m
矩阵
limit

输出格式

一个整数,最大面积。

题解

暴力做法 :枚举上下左右四边界,O(n²m²),超时。

优化:枚举上下边界,对每一列维护该区间内的最大最小值,转化为"一维滑动窗口"问题。

python 复制代码
from collections import deque

n, m = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
limit = int(input())

s = 0

# 枚举上下边界
for top in range(n):
    # 初始化每列的最大最小值
    col_min = a[top][:]
    col_max = a[top][:]
    
    for bottom in range(top, n):
        # 更新每列的最大最小值
        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])
        
        # 滑动窗口:找最长连续列,使得 max-min <= limit
        left = 0
        window_min = deque()  # 维护窗口最小值(存下标)
        window_max = deque()  # 维护窗口最大值(存下标)
        
        for right in range(m):
            # 维护单调递增队列(最小值)
            while window_min and col_min[window_min[-1]] >= col_min[right]:
                window_min.pop()
            window_min.append(right)
            
            # 维护单调递减队列(最大值)
            while window_max and col_max[window_max[-1]] <= col_max[right]:
                window_max.pop()
            window_max.append(right)
            
            # 收缩左边界,直到满足条件
            while left <= right:
                cur_min = col_min[window_min[0]]
                cur_max = col_max[window_max[0]]
                if cur_max - cur_min <= limit:
                    break
                left += 1
                if window_min[0] < left:
                    window_min.popleft()
                if window_max[0] < left:
                    window_max.popleft()
            
            # 更新答案
            area = (bottom - top + 1) * (right - left + 1)
            s = max(s, area)

print(s)

关键细节

坑点 说明
滑动窗口维护的是列 不是元素,是上下边界确定的列区间
单调队列存下标 不是存值,方便判断元素是否滑出窗口
面积计算 (bottom-top+1) * (right-left+1)

例题 4:扫地机器人(蓝桥杯 P199)

项目 内容
链接 https://www.lanqiao.cn/problems/199/learning/
类型 二分答案 + 贪心模拟
核心 模拟机器人清扫过程、判断时间是否足够

题目描述

n 个垃圾站排成一行,k 个机器人。第 i 个机器人在位置 a[i]。每个机器人可以左右清扫,速度为1(走一格1单位时间)。求清扫完所有垃圾的最小时间。

输入格式

复制代码
n k
a[1]
a[2]
...
a[k]

输出格式

一个整数,最小时间。

题解

贪心策略:机器人按位置排序,每个机器人先往左清扫(如果左边还有垃圾),再往右清扫。

check(t) :给定时间 t,能否清扫完 [1, n]

python 复制代码
n, k = map(int, input().split())
a = [int(input()) for _ in range(k)]
a.sort()

def check(x):
    """给定时间x,能否清扫完所有垃圾"""
    cnt = 0  # 当前清扫到的最右位置
    
    for i in range(k):
        cur = x  # 当前机器人剩余时间
        
        # 先往左清扫
        if cnt < a[i] - 1:
            # 需要走到左边未清扫的区域
            need = 2 * (a[i] - 1 - cnt)  # 走过去再回来
            cur -= need
        
        if cur < 0:
            return False  # 时间不够走到左边
        
        # 往右清扫,计算能扫多远
        # 从a[i]出发,剩余时间cur,可以向右走 cur//2 格(来回)
        # 或者直接走到右边:如果不需要回来,可以走cur格
        # 实际上机器人最后不需要回到起点,所以优化:
        # 先左后右,最后停在右边,节省回来的时间
        
        # 简化:假设先花时间去左边,然后剩下的时间往右
        # 往右能扫到 a[i] + cur//2(如果之前去了左边)
        # 或者 a[i] + cur(如果左边已经扫完)
        
        if cnt >= a[i] - 1:  # 左边已经扫完
            cnt = max(cnt, a[i] + cur)
        else:
            cnt = max(cnt, a[i] + cur // 2)
    
    return cnt >= n

left, right = 0, 2 * n
ans = 0

while left <= right:
    mid = (left + right) // 2
    if check(mid):
        ans = mid
        right = mid - 1
    else:
        left = mid + 1

print(ans)

关键细节

坑点 说明
先左后右的代价 去左边需要来回,代价 2*距离
最后不需要回来 往右清扫时,最后可以停在右边,只花单程
cnt 的含义 当前已经清扫到的最右位置
机器人排序 必须按位置排序,贪心从左到右处理

例题 5:区间覆盖(蓝桥杯 P111)

项目 内容
链接 https://www.lanqiao.cn/problems/111/learning/
类型 二分答案 + 贪心
核心 浮点二分、区间覆盖、精度处理

题目描述

n 个区间 [u, v],每个区间可以扩展 mid(变为 [u-mid, v+mid])。求最小的 mid,使得扩展后的区间能覆盖 [0, 20000]

输入格式

复制代码
n
u1 v1
u2 v2
...
un vn

输出格式

最小的 mid,如果是整数输出整数,否则保留一位小数。

题解

浮点二分 :答案可能是 .5(因为输入是整数,扩展 mid 后边界可能是 x.5)。

check(mid):贪心选择能覆盖当前点的、右端点最远的区间。

python 复制代码
n = int(input())
qujian = []
for i in range(n):
    u, v = map(int, input().split())
    qujian.append((u * 2, v * 2))  # 乘2避免浮点

qujian.sort(key=lambda x: (x[1], x[0]))

def check(mid):
    cur = 0  # 当前覆盖到的最右位置(乘2后的坐标)
    visited = [0] * n
    
    while True:
        flag = False
        for i in range(n):
            p = qujian[i]
            if not visited[i] and p[0] - mid <= cur and p[1] + mid >= cur:
                # 可以覆盖当前点,选择能延伸最远的
                if p[0] + mid >= cur:
                    cur = max(cur, p[1] - p[0] + cur)  # 从cur开始延伸
                else:
                    cur = max(cur, p[1] + mid)
                flag = True
                visited[i] = 1
                break  # 贪心:选第一个能覆盖的
        
        if not flag or cur >= 40000:  # 20000*2
            break
    
    return cur >= 40000

# 二分答案(整数,最后除2)
left, right = 0, int(2e4)
while left < right:
    mid = (left + right) // 2
    if check(mid):
        right = mid
    else:
        left = mid + 1

# 输出处理
if right % 2 == 0:
    print(right // 2)
else:
    print(right / 2)

关键细节

坑点 说明
乘2处理精度 避免浮点二分,用整数二分最后除2
贪心策略 选能覆盖当前点且延伸最远的区间
visited 数组 每个区间只能用一次
输出格式 整数或一位小数

📊 今日刷题总结

题号 考点 结合算法 难度 核心技巧
P2143 中位数 二分 + 数学 ⭐⭐⭐ bisect统计大小关系
P2145 阶乘零 二分 + 数论 ⭐⭐⭐ 勒让德公式、验证存在性
P2147 最大子矩阵 枚举 + 滑动窗口 ⭐⭐⭐⭐ 单调队列维护最值
P199 扫地机器人 二分 + 贪心模拟 ⭐⭐⭐⭐ 先左后右、时间分配
P111 区间覆盖 二分 + 贪心 ⭐⭐⭐⭐ 乘2避浮点、覆盖延伸

🎯 二分答案进阶技巧

技巧 适用场景 例题
乘2/乘10避浮点 答案含0.5或小数 P111
先验证存在性 可能无解 P2145
贪心模拟 过程复杂,需要逐步验证 P199
转化为判定问题 求最优值 所有二分答案
结合数据结构 需要快速统计 P2143用bisect

💡 今日心得

  1. 二分答案不是孤立算法 :必须结合 check() 函数,而 check() 可能是贪心、模拟、数学公式
  2. 浮点精度用整数替代:P111乘2、P2145用大整数,避免浮点误差
  3. 验证最后答案 :P2145必须 check(ans)==k,防止跳跃导致无解
  4. 贪心策略要正确:P199先左后右,P111选延伸最远的区间,都要证明正确性
  5. 滑动窗口维护最值 :P2147用单调队列,将 O(m) 优化到 O(1)
相关推荐
绛橘色的日落(。・∀・)ノ1 小时前
机器学习 逻辑回归
算法·机器学习·逻辑回归
阿正呀1 小时前
Redis如何处理数据持久化与主从切换的冲突_确保选主期间的数据安全落盘.txt
jvm·数据库·python
AI精钢1 小时前
把 Markdown 笔记变成可问答的知识图谱:本地 Graph RAG 工具 Kwipu 实测
人工智能·笔记·python·aigc·知识图谱
测绘第一深情1 小时前
在vscode中使用codex教程(个人安装经验)
数据结构·ide·vscode·python·算法·计算机视觉·编辑器
m0_470857641 小时前
php中的foreach循环?_?PHP中foreach循环的语法结构与遍历数组对象详解.txt
jvm·数据库·python
彳亍1011 小时前
HTML5中Canvas局部刷新区域重绘的算法优化
jvm·数据库·python
2301_779622411 小时前
为什么宝塔面板网站无法正常连接外部远程数据库_检查服务器安全组放行端口并开启IP授权
jvm·数据库·python
2401_833033621 小时前
Go语言怎么做密码加密_Go语言bcrypt密码哈希教程【总结】
jvm·数据库·python
X56611 小时前
mysql索引基数统计更新不及时_mysqlANALYZE分析表操作
jvm·数据库·python