洛谷P3662 [USACO17FEB] Why Did the Cow Cross the Road II S-队列

P3662 [USACO17FEB] Why Did the Cow Cross the Road II S

题目描述

穿过 Farmer John 农场的长路上有NNN个人行横道,方便地用编号1…N1 \ldots N1N标识(1≤N≤100,0001 \leq N \leq 100,0001N100,000)。为了让奶牛能够通过这些横道过马路,FJ 安装了电子过马路信号灯,当奶牛可以安全过马路时,信号灯会显示绿色的奶牛图标,否则显示红色。不幸的是,一场大雷暴损坏了他的一些信号灯。给定损坏信号灯的列表,请计算 FJ 需要修复的最少信号灯数量,以便存在至少KKK个连续的信号灯正常工作。

输入格式

输入的第一行包含NNNKKKBBB1≤B,K≤N1 \leq B, K \leq N1B,KN)。接下来的BBB行每行描述一个损坏信号灯的 ID 编号。

输出格式

请计算需要修复的最少信号灯数量,以便在道路上某处存在一个长度为KKK的连续正常工作信号灯块。

输入输出样例 #1

输入 #1

10 6 5
2
10
1
5
9

输出 #1

1

解题思路

本题需要找到最少需要修复的信号灯数量,使得存在至少 K 个连续的正常工作的信号灯。核心思路是利用滑动窗口技术统计每个长度为 K 的连续子序列中的坏灯数量,并找出最小值。

关键步骤

  1. 标记坏灯位置:使用布尔数组标记哪些灯是坏的。
  2. 滑动窗口统计
    • 初始化窗口:计算前 K 个灯中的坏灯数量。
    • 移动窗口:每次向右移动一位,减去离开窗口的灯的状态,加上新进入窗口的灯的状态。
  3. 动态更新最小值:在窗口移动过程中,记录窗口内坏灯数量的最小值。

C++代码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int N, K, B;
    cin >> N >> K >> B;
    
    vector<bool> broken(N + 1, false); // 标记坏灯位置
    
    // 读入坏灯位置并标记
    for (int i = 0; i < B; i++) {
        int x;
        cin >> x;
        broken[x] = true;
    }
    
    int current_broken = 0; // 当前窗口内的坏灯数量
    // 计算初始窗口 [1, K] 中的坏灯数量
    for (int i = 1; i <= K; i++) {
        if (broken[i]) {
            current_broken++;
        }
    }
    
    int min_repair = current_broken; // 初始化最小修复数量
    
    // 滑动窗口:从 K+1 到 N
    for (int i = K + 1; i <= N; i++) {
        // 移除窗口左边界元素(i-K 位置)
        if (broken[i - K]) {
            current_broken--;
        }
        // 添加新元素(i 位置)
        if (broken[i]) {
            current_broken++;
        }
        // 更新最小修复数量
        min_repair = min(min_repair, current_broken);
    }
    
    cout << min_repair << endl;
    
    return 0;
}

代码说明

  1. 输入处理
    • 读取信号灯总数 N、要求连续正常灯数 K、坏灯数量 B
    • 创建布尔数组 broken 标记坏灯位置。
  2. 初始窗口计算
    • 计算第一个窗口(位置 1 到 K)中的坏灯数量 current_broken
    • 初始化最小修复数量 min_repair = current_broken
  3. 滑动窗口移动
    • 从位置 K+1 开始向右移动窗口:
      • 移除左边界元素:如果位置 i-K 是坏灯,则当前坏灯数减 1。
      • 添加新元素:如果位置 i 是坏灯,则当前坏灯数加 1。
    • 每次移动后更新最小修复数量。
  4. 输出结果:打印需要修复的最小信号灯数量。

复杂度分析

  • 时间复杂度:O(N),只需遍历信号灯序列一次。
  • 空间复杂度:O(N),用于存储坏灯标记的数组。

示例解析

输入10 6 5,坏灯位置:2, 10, 1, 5, 9
处理过程

  • 初始窗口 [1, 6]:坏灯位置 1, 2, 5 → 坏灯数 3。
  • 窗口 [2, 7]:移除位置 1(坏),添加位置 7(好)→ 坏灯数 2。
  • 窗口 [3, 8]:移除位置 2(坏),添加位置 8(好)→ 坏灯数 1(最小值)。
  • 窗口 [4, 9]:移除位置 3(好),添加位置 9(坏)→ 坏灯数 2。
  • 窗口 [5, 10]:移除位置 4(好),添加位置 10(坏)→ 坏灯数 2。

输出1(最小修复数量)

解题思路

本题需要找到最少需要修复的信号灯数量,使得存在至少 K 个连续的正常工作的信号灯。核心思路是利用滑动窗口技术统计每个长度为 K 的连续子序列中的坏灯数量,并找出最小值。具体步骤如下:

  1. 标记坏灯位置:使用布尔数组标记哪些位置的灯是坏的。
  2. 滑动窗口统计
    • 使用队列存储当前窗口内的坏灯位置
    • 当窗口移动时:
      • 检查队列头部是否是需要离开窗口的位置(i-K),如果是则出队
      • 如果新加入位置是坏灯,则将其入队
  3. 动态更新最小值:在窗口形成后(i≥K),记录队列大小(即当前窗口坏灯数)的最小值

C++代码实现

#include <iostream>
#include <queue>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int N, K, B;
    cin >> N >> K >> B;
    
    vector<bool> broken(N + 1, false); // 标记坏灯位置
    
    // 读入坏灯位置并标记
    for (int i = 0; i < B; i++) {
        int x;
        cin >> x;
        broken[x] = true;
    }
    
    queue<int> q; // 存储当前窗口内的坏灯位置
    int min_repair = 1e6; // 初始化为大数
    
    for (int i = 1; i <= N; i++) {
        // 窗口移动:移除离开窗口的位置 (i-K)
        if (i > K) {
            if (!q.empty() && q.front() == i - K) {
                q.pop();
            }
        }
        
        // 如果当前位置是坏灯,加入队列
        if (broken[i]) {
            q.push(i);
        }
        
        // 当窗口形成时 (i >= K),更新最小值
        if (i >= K) {
            min_repair = min(min_repair, (int)q.size());
        }
    }
    
    cout << min_repair << endl;
    
    return 0;
}

代码说明

  1. 输入处理
    • 读取信号灯总数 N、要求连续正常灯数 K、坏灯数量 B
    • 创建布尔数组 broken 标记坏灯位置
  2. 滑动窗口管理
    • 使用队列 q 存储当前窗口内的坏灯位置
    • 遍历每个信号灯位置 i (1 到 N):
      • 移除过期位置:当 i > K 时,检查队头是否等于 i-K(离开窗口的位置),是则出队
      • 添加新位置:如果位置 i 是坏灯,则入队
      • 更新最小值:当 i ≥ K 时(窗口已形成),更新最小修复数量
  3. 输出结果:打印需要修复的最小信号灯数量

复杂度分析

  • 时间复杂度:O(N),每个位置最多入队和出队各一次
  • 空间复杂度:O(N),用于存储坏灯标记和队列

示例解析

输入10 6 5,坏灯位置:2, 10, 1, 5, 9
处理过程

  • i=1:加入位置1(坏灯),队列=[1]
  • i=2:加入位置2(坏灯),队列=[1,2]
  • i=6:窗口[1,6]形成,队列大小=3
  • i=7:移除位置1(i-K=1),队列=[2,5];更新最小值=2
  • i=8:移除位置2(i-K=2),队列=[5];更新最小值=1
  • i=9:加入位置9(坏灯),队列=[5,9];最小值保持1
  • i=10:加入位置10(坏灯),队列=[5,9,10];最小值保持1

输出1(最小修复数量)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信奥源老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值