总题单
本部分总题单如下
【腾讯文档】副本-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++代码实现(滑动窗口 + 队列)
#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;
}
🧠 示例解析:
输入:
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)
所以输出:
1
⏱️ 时间复杂度分析:
- 每个坏灯最多入队、出队一次 → O(N)
- 不需要额外的前缀和数组 → 更节省内存
- 对于每个窗口只需维护队列长度 → 高效且直观
代码2-前缀和
#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语句,
内容详见模板代码如下。
#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,就可以修改标准流文件的默认值,实现重定向。
#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;
}