P3662 [USACO17FEB] Why Did the Cow Cross the Road II S
题目描述
穿过 Farmer John 农场的长路上有NNN个人行横道,方便地用编号1…N1 \ldots N1…N标识(1≤N≤100,0001 \leq N \leq 100,0001≤N≤100,000)。为了让奶牛能够通过这些横道过马路,FJ 安装了电子过马路信号灯,当奶牛可以安全过马路时,信号灯会显示绿色的奶牛图标,否则显示红色。不幸的是,一场大雷暴损坏了他的一些信号灯。给定损坏信号灯的列表,请计算 FJ 需要修复的最少信号灯数量,以便存在至少KKK个连续的信号灯正常工作。
输入格式
输入的第一行包含NNN、KKK和BBB(1≤B,K≤N1 \leq B, K \leq N1≤B,K≤N)。接下来的BBB行每行描述一个损坏信号灯的 ID 编号。
输出格式
请计算需要修复的最少信号灯数量,以便在道路上某处存在一个长度为KKK的连续正常工作信号灯块。
输入输出样例 #1
输入 #1
10 6 5
2
10
1
5
9
输出 #1
1
解题思路
本题需要找到最少需要修复的信号灯数量,使得存在至少 K 个连续的正常工作的信号灯。核心思路是利用滑动窗口技术统计每个长度为 K 的连续子序列中的坏灯数量,并找出最小值。
关键步骤:
- 标记坏灯位置:使用布尔数组标记哪些灯是坏的。
- 滑动窗口统计:
- 初始化窗口:计算前 K 个灯中的坏灯数量。
- 移动窗口:每次向右移动一位,减去离开窗口的灯的状态,加上新进入窗口的灯的状态。
- 动态更新最小值:在窗口移动过程中,记录窗口内坏灯数量的最小值。
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;
}
代码说明
- 输入处理:
- 读取信号灯总数
N
、要求连续正常灯数K
、坏灯数量B
。 - 创建布尔数组
broken
标记坏灯位置。
- 读取信号灯总数
- 初始窗口计算:
- 计算第一个窗口(位置 1 到 K)中的坏灯数量
current_broken
。 - 初始化最小修复数量
min_repair = current_broken
。
- 计算第一个窗口(位置 1 到 K)中的坏灯数量
- 滑动窗口移动:
- 从位置
K+1
开始向右移动窗口:- 移除左边界元素:如果位置
i-K
是坏灯,则当前坏灯数减 1。 - 添加新元素:如果位置
i
是坏灯,则当前坏灯数加 1。
- 移除左边界元素:如果位置
- 每次移动后更新最小修复数量。
- 从位置
- 输出结果:打印需要修复的最小信号灯数量。
复杂度分析
- 时间复杂度: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 的连续子序列中的坏灯数量,并找出最小值。具体步骤如下:
- 标记坏灯位置:使用布尔数组标记哪些位置的灯是坏的。
- 滑动窗口统计:
- 使用队列存储当前窗口内的坏灯位置
- 当窗口移动时:
- 检查队列头部是否是需要离开窗口的位置(i-K),如果是则出队
- 如果新加入位置是坏灯,则将其入队
- 动态更新最小值:在窗口形成后(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;
}
代码说明
- 输入处理:
- 读取信号灯总数
N
、要求连续正常灯数K
、坏灯数量B
- 创建布尔数组
broken
标记坏灯位置
- 读取信号灯总数
- 滑动窗口管理:
- 使用队列
q
存储当前窗口内的坏灯位置 - 遍历每个信号灯位置
i
(1 到 N):- 移除过期位置:当
i > K
时,检查队头是否等于i-K
(离开窗口的位置),是则出队 - 添加新位置:如果位置
i
是坏灯,则入队 - 更新最小值:当
i ≥ K
时(窗口已形成),更新最小修复数量
- 移除过期位置:当
- 使用队列
- 输出结果:打印需要修复的最小信号灯数量
复杂度分析
- 时间复杂度: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
(最小修复数量)