信奥赛-刷题笔记-队列篇-T3-P3662Why Did the Cow Cross the Road II S

总题单

本部分总题单如下

【腾讯文档】副本-CSP-JS+NOI 题单 (未完待续)

https://docs.qq.com/sheet/DSmJuVXR4RUNVWWhW?tab=BB08J2

队列篇题单

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

题目描述

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

输入格式

输入的第一行包含 N N N、 K K K 和 B B B( 1 ≤ B , K ≤ N 1 \leq B, K \leq N 1≤B,K≤N)。接下来的 B B B 行每行描述一个损坏信号灯的 ID 编号。

输出格式

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

输入输出样例 #1

输入 #1

复制代码
10 6 5
2
10
1
5
9

输出 #1

复制代码
1

代码1-滑动窗口+队列

当然可以!我们可以使用 滑动窗口 + 单调队列 的方式来优化这个问题。虽然这道题的规模不是特别大(N ≤ 100000),但使用滑动窗口 + 队列结构可以让我们更高效地处理这类问题。


✅ 思路说明(滑动窗口 + 队列解法)

我们要找出一个长度为 K 的区间,使得这个区间中坏灯的数量最少。

我们维护一个滑动窗口,窗口大小固定为 K,从左到右滑动整个数组,统计窗口内坏灯的数量。

滑动窗口 + 队列

  • 我们用一个队列保存当前窗口内的所有坏灯的位置。
  • 每次窗口向右移动一位:
    • 如果最左边的坏灯在窗口外,则弹出它。
    • 如果新的坏灯进入窗口,则加入队列。
  • 队列的长度就是当前窗口内坏灯的数量。
  • 维护最小值即可得到答案。

✅ C++代码实现(滑动窗口 + 队列)

cpp 复制代码
#include <iostream>
#include <queue>
#include <vector>
#include <cstdio>
using namespace std;

int main() {
    int n, k, b;
    cin >> n >> k >> b;

    vector<bool> is_broken(n + 2, false); // 标记哪些位置是坏灯(从1开始编号)
    queue<int> broken_in_window;          // 存储当前窗口中的坏灯位置

    for (int i = 0; i < b; ++i) {
        int x;
        scanf("%d", &x);
        is_broken[x] = true; // 标记坏灯位置
    }

    int min_repair = k; // 最多修复k个灯(初始设为最大值)

    // 滑动窗口 [l, r]
    for (int r = 1; r <= n; ++r) {
        // 当前灯是坏的,加入队列
        if (is_broken[r]) {
            broken_in_window.push(r);
        }

        // 窗口大小超过k时,左指针向右移
        while (r - broken_in_window.front() + 1 > k) {
            broken_in_window.pop(); // 移除窗口外的坏灯
        }

        // 当窗口大小正好是k时,计算当前窗口需要修复的灯数
        if (r >= k) {
            min_repair = min(min_repair, (int)broken_in_window.size());
        }
    }

    cout << min_repair << endl;

    return 0;
}

🧠 示例解析:

输入:

txt 复制代码
10 6 5
2
10
1
5
9

坏灯位置:1、2、5、9、10

遍历所有长度为6的窗口:

  • 1~6:有4个坏灯(1,2,5)
  • 2~7:有3个坏灯(2,5)
  • ...
  • 5~10:有3个坏灯(5,9,10)
  • 6~11:只有1个坏灯(10)

所以输出:

txt 复制代码
1

⏱️ 时间复杂度分析:

  • 每个坏灯最多入队、出队一次 → O(N)
  • 不需要额外的前缀和数组 → 更节省内存
  • 对于每个窗口只需维护队列长度 → 高效且直观

代码2-前缀和

cpp 复制代码
#include<iostream>     // 输入输出流库
#include<cstdio>       // 提供C风格的输入输出函数(如scanf)
#include<cstring>      // 字符串操作函数(本程序未使用)
#include<cstdlib>      // 标准库函数,例如exit()
#include<cmath>        // 数学运算函数(本程序未使用)
#include<algorithm>    // 提供常用算法函数,如min、max等
#define maxn 100201    // 定义一个最大数组长度常量,略大于题目要求的最大N值
using namespace std;   // 使用标准命名空间,避免每次写std::

int n, k, b;           // 分别表示总信号灯数量、需要连续正常的灯数、损坏的灯数量
int vis[maxn];         // 标记数组,vis[i]为1表示第i个信号灯是坏的
int sum[maxn];         // 前缀和数组,记录前i个中有多少个坏灯
int ans = 0x7fffffff;  // 初始化答案为一个极大值(相当于INT_MAX)

int main()
{
    cin >> n >> k >> b;  // 输入n, k, b

    for(int i = 1; i <= b; i++)  // 循环读取b个坏灯的位置
    {
        int x;
        scanf("%d", &x);  // 输入坏灯编号
        vis[x] = 1;       // 将该位置标记为坏灯
    }

    // 构建前缀和数组sum
    // sum[i] 表示从第1个到第i个灯中,有多少个坏灯
    for(int i = 1; i <= n; i++)
        sum[i] += sum[i-1] + vis[i];  // 每次加上当前是否是坏灯(0或1)

    // 遍历所有长度为k的区间,计算其中坏灯的数量,并找出最小值
    for(int i = 1; i <= n - k + 1; i++)
    {
        // 区间是 [i, i+k-1]
        // sum[i+k-1] - sum[i-1] 表示这个区间内坏灯的数量
        // 我们要修复这些坏灯,所以这就是需要修复的灯数
        ans = min(ans, sum[i + k - 1] - sum[i - 1]);
    }

    cout << ans << endl;  // 输出最终结果:最少需要修复的灯数

    return 0;
}

✅ 总结

方法 时间复杂度 空间复杂度 是否适合本题
前缀和 O(N) O(N) ✔ 推荐
滑动窗口+队列 O(N) O(B)(B是坏灯数量) ✔ 更优雅,适合进阶

现场真题注意事项

https://cspoj.com/contest.php?cid=1002Fus5yz4x3EcSJH1Z

注意事项

文件名(程序名和输入输出文件名)必须使用英文小写。(提交必须使用freopen()进行提交)

C/C++ 中函数 main() 的返回值类型必须是 int,程序正常结束时的返回值必须是0。

提交的程序代码文件的放置位置请参考各省的具体要求。

因违反以上三点而出现的错误或问题,申述时一律不予受理。

若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)。

程序可使用的栈空间内存限制与题目的内存限制一致。

全国统一评测时采用的机器配置为:Inter® Core™ i7-8700K CPU @3.70GHz,内存 32GB。上述时限以此配置为准。

只提供 Linux 格式附加样例文件。

评测在当前最新公布的 NOI Linux 下进行,各语言的编译器版本以此为准

假设输入样例数据存在文件test.in中,输出样例数据存在文件test.out中,

则在CSP、NOI等比赛的代码中,需添加freopen、fclose语句,

内容详见模板代码如下。

cpp 复制代码
#include <bits/stdc++.h>
#include<cstdio>//必须包含cstdio头文件
#include<iostream>
using namespace std;
 
int main(){
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	
	cout<<"Hello NOI"<<endl;
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}

复制

下面为函数的简介,详细可参见 http://www.cplusplus.com/reference/clibrary/cstdio/freopen.html

函数名:freopen

声明:FILE freopen( const char path, const char mode, FILE stream );

所在文件: stdio.h

参数说明:

path: 文件名,用于存储输入输出的自定义文件名。

mode: 文件打开的模式。和fopen中的模式(如r-只读, w-写)相同。

stream: 一个文件,通常使用标准流文件。

返回值:成功,则返回一个path所指定文件的指针;失败,返回NULL。(一般可以不使用它的返回值)

功能:实现重定向,把预定义的标准流文件定向到由path指定的文件中。标准流文件具体是指stdin、stdout和stderr。其中stdin是标准输入流,默认为键盘;stdout是标准输出流,默认为屏幕;stderr是标准错误流,一般把屏幕设为默认。通过调用freopen,就可以修改标准流文件的默认值,实现重定向。

cpp 复制代码
#include<iostream>
#include<cstdio>
using namespace std;
int main(){
    freopen("7532.in", "r", stdin);
    freopen("7532.out", "w", stdout);
    //原来的代码保持不变
    double a, b, r;
    int k;
    cin >> a >> b;
    k = int(a/b);
    r = a - b * k;
    printf("%g", r);
    //-------------
    fclose(stdin);
    fclose(stdout);
    return 0;
}
相关推荐
奔跑吧 android几秒前
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
android·bluetooth·bt·aosp13·storagemodule
田一一一4 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
愚戏师7 小时前
Linux复习笔记(六)shell编程
linux·笔记·shell
.(ᗜ ˰ ᗜ) .7 小时前
机器学习笔记2
笔记
小葡萄20258 小时前
黑马程序员c++2024版笔记 第一章 变量和基本类型
笔记·c++20
顾子茵8 小时前
计算机图形学基础--Games101笔记(一)数学基础与光栅化
笔记·图形渲染
黄暄8 小时前
初识计算机网络。计算机网络基本概念,分类,性能指标
笔记·学习·计算机网络·考研
androidwork8 小时前
掌握 Kotlin Android 单元测试:MockK 框架深度实践指南
android·kotlin
WarPigs8 小时前
Unity光照笔记
笔记·unity·游戏引擎
田一一一8 小时前
Android framework 中间件开发(二)
android·中间件·framework