信奥赛-刷题笔记-栈篇-T3-P1318 积水面积

总题单

本部分总题单如下

【腾讯文档】副本-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,结果正确。


编译运行说明

  1. 将代码保存为.cpp文件(如water.cpp)。
  2. 使用g++编译:g++ water.cpp -o water
  3. 运行程序:./water
  4. 输入测试用例,即可得到结果。

总结

  1. 左右最大高度法:逻辑直观,新手易理解,时间/空间复杂度均为O(n),适合入门掌握。
  2. 单调栈法:时间复杂度O(n),空间复杂度最坏O(n)(实际更优),通过维护单调栈一次遍历完成计算,效率更高。
  3. 核心规则:任意位置的积水量 = 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;
}
相关推荐
zhqh1002 小时前
openclaw+qwen(笔记,非教程)
笔记
JELEE.2 小时前
drf笔记与源码解析
笔记·python·django·drf
鄭郑2 小时前
STM32学习笔记--SPI初始化与数据收发(01)
笔记·stm32·学习
花间相见2 小时前
【知识库笔记管理】—— 使用PARA方法高效建立自己个人知识库
大数据·笔记
浅念-2 小时前
C++11 核心知识点整理
开发语言·数据结构·c++·笔记·算法
别催小唐敲代码2 小时前
个人笔记网站搭建完整教程
笔记·学习·个人博客
妄汐霜3 小时前
小白学习笔记(ES6)
笔记·学习
zhaoyin19943 小时前
Vue面试题笔记
javascript·vue.js·笔记
猹叉叉(学习版)3 小时前
【ASP.NET CORE】 10. 数据校验
笔记·后端·c#·asp.net·.netcore