总题单
本部分总题单如下
【腾讯文档】副本-CSP-JS+NOI 题单 (未完待续)
https://docs.qq.com/sheet/DSmJuVXR4RUNVWWhW?tab=BB08J2
参考:
https://zhuanlan.zhihu.com/p/694077061
https://hit9.dev/post/monotonic-stack
栈篇题单

P1318 积水面积
题目描述
一组正整数,分别表示由正方体叠起的柱子的高度。若某高度值为 x x x,表示由 x x x 个正立方的方块叠起(如下图, 0 ≤ x ≤ 5000 0 \le x \le 5000 0≤x≤5000)。找出所有可能积水的地方(图中蓝色部分),统计它们可能积水的面积总和(计算的是图中的横截面积。一个立方体的位置,为一个单位面积)。
如图:柱子高度变化为 0 1 0 2 1 2 0 0 2 0。

图中蓝色部分为积水面积,共有 6 6 6 个单位面积积水。
输入格式
两行,第一行 n n n,表示有 n n n 个数( 3 ≤ n ≤ 10000 3 \le n \le 10000 3≤n≤10000)。第 2 2 2 行连续 n n n 个数表示依次由正方体叠起的高度,保证首尾为 0 0 0。
输出格式
一个数,可能积水的面积。
输入输出样例 #1
输入 #1
10
0 1 0 2 1 2 0 0 2 0
输出 #1
6
C++ 解法(两种方法)
我会为你提供两种C++实现方案:左右最大高度法 (直观易懂)和单调栈法(更优),你可以根据自己的掌握程度选择学习。
方法1:左右最大高度法
代码实现
cpp
#include <iostream>
#include <vector>
#include <algorithm> // 用于max/min函数
using namespace std;
int main() {
int n;
cin >> n;
vector<int> height(n);
for (int i = 0; i < n; ++i) {
cin >> height[i];
}
if (n < 3) { // 至少3个柱子才可能积水
cout << 0 << endl;
return 0;
}
// 1. 计算每个位置左侧的最大高度
vector<int> left_max(n, 0);
for (int i = 1; i < n; ++i) {
left_max[i] = max(left_max[i-1], height[i-1]);
}
// 2. 计算每个位置右侧的最大高度
vector<int> right_max(n, 0);
for (int i = n-2; i >= 0; --i) {
right_max[i] = max(right_max[i+1], height[i+1]);
}
// 3. 累加所有位置的积水量
int total = 0;
for (int i = 0; i < n; ++i) {
int water = min(left_max[i], right_max[i]) - height[i];
if (water > 0) {
total += water;
}
}
cout << total << endl;
return 0;
}
代码解释
left_max数组:从左到右遍历,存储每个位置左侧的最大柱子高度(不包含自身)。right_max数组:从右到左遍历,存储每个位置右侧的最大柱子高度(不包含自身)。- 核心逻辑:遍历每个位置,计算该位置能存储的水量(仅累加正数),最终总和即为答案。
测试样例
输入:
10
0 1 0 2 1 2 0 0 2 0
输出:6,与题目样例一致。
方法2:单调栈法(空间更优)
代码实现
cpp
#include <iostream>
#include <vector>
#include <stack> // 栈容器
using namespace std;
int main() {
int n;
cin >> n;
vector<int> height(n);
for (int i = 0; i < n; ++i) {
cin >> height[i];
}
stack<int> st; // 存储柱子索引,栈内索引对应的高度单调递减
int total = 0;
for (int i = 0; i < n; ++i) {
// 当前柱子高度 > 栈顶柱子高度,说明能形成积水凹槽
while (!st.empty() && height[i] > height[st.top()]) {
// 弹出凹槽底部的索引
int bottom_idx = st.top();
st.pop();
if (st.empty()) { // 没有左边界,无法积水
break;
}
// 左边界是新的栈顶
int left_idx = st.top();
// 计算积水高度:左右边界较小值 - 凹槽底部高度
int h = min(height[left_idx], height[i]) - height[bottom_idx];
// 计算积水宽度:当前索引 - 左边界索引 - 1
int w = i - left_idx - 1;
// 累加积水量
total += h * w;
}
// 压入当前索引,保持栈的单调递减
st.push(i);
}
cout << total << endl;
return 0;
}
代码解释
- 栈中存储的是柱子索引,而非高度,目的是方便计算积水的宽度。
- 核心循环:当当前柱子高度大于栈顶高度时,说明栈顶位置是"凹槽底部",弹出后计算该凹槽的积水量:
- 左边界:新的栈顶元素(栈不能为空)。
- 积水高度:左右边界的较小值减去凹槽底部高度。
- 积水宽度:当前索引减去左边界索引再减1。
- 若当前柱子高度不大于栈顶高度,则将索引压入栈,保持栈的单调递减特性。
测试样例
输入同上,输出仍为6,结果正确。
编译运行说明
- 将代码保存为
.cpp文件(如water.cpp)。 - 使用g++编译:
g++ water.cpp -o water。 - 运行程序:
./water。 - 输入测试用例,即可得到结果。
总结
- 左右最大高度法:逻辑直观,新手易理解,时间/空间复杂度均为O(n),适合入门掌握。
- 单调栈法:时间复杂度O(n),空间复杂度最坏O(n)(实际更优),通过维护单调栈一次遍历完成计算,效率更高。
- 核心规则:任意位置的积水量 = min(左侧最高高度, 右侧最高高度) - 当前高度(结果>0时有效)。
现场真题注意事项
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
声明:
cpp
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;
}