题解 | #灌注水箱#

灌注水箱

https://www.nowcoder.com/practice/6207bb338bb149009720a1b39315d8c4

题目链接

灌注水箱

题目描述

有一个水箱,从左到右有 个挡板,第 个挡板的高度为 。水从最左侧以每秒 1 个单位体积的速度注入。水在一个区域内的水位是均匀的。当一个区域的水位超过其右侧挡板的高度时,水会溢出到下一个区域。求每个挡板被溢过的最早时刻。

解题思路

直接模拟每一秒的水流过程显然会超时。这是一个动态规划问题,可以通过数据结构进行优化。

1. 核心模型:盆地填充

我们可以将注水过程看作是填充一系列“盆地”。每个挡板 都是其左侧一个盆地的右边界。这个盆地的左边界,则是由左侧第一个高度大于或等于 的挡板 决定的。如果不存在这样的挡板,我们可以认为最左侧有一个无限高的 0 号挡板。

2. 建立递推关系

为恰好将水蓄满到可以溢出第 个挡板(即水位达到 )时,所需要的总注水量。由于注水速度恒定,这个体积在数值上等于所需的时间。

  • 要开始填充以 为右边界的盆地 (L_i, i],必须先完成对 左侧所有盆地的填充,直到水可以溢出 。这部分所需的水量为
  • 当水开始溢出 后,它会流入 (L_i, i] 这个新的区域。这个区域的宽度为
  • 要让水溢出挡板 ,我们需要将这个宽度为 的区域从底部填充到高度 。这需要的新增水量为
  • 因此,总注水量 等于填充到 的水量与填充新区域的水量之和。我们得到递推公式:
  • 我们定义 作为递推的起点。

3. 使用单调栈寻找

问题转化为如何为每个 高效地找到 。这是“寻找左侧第一个更高/相等元素”的经典问题,可以使用单调栈 的时间内解决。
我们维护一个存储挡板下标的栈,并保持栈中下标对应的挡板高度是单调递增的。

4. 最终答案

  • 是在第 秒末,水位恰好达到 所需的总水量/时间。
  • 根据题目定义,水在下一个时间单位,即第 秒,才会“溢过”挡板。
  • 因此,第 个挡板的溢出时刻是

算法步骤

  1. 使用单调栈,从左到右遍历挡板,在 时间内计算出每个挡板 对应的左边界
  2. 根据递推公式 ,在 时间内计算出所有
  3. 输出每个挡板的答案

代码

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;

    vector<long long> h(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> h[i];
    }

    vector<int> left_taller(n + 1, 0);
    stack<int> s;

    for (int i = 1; i <= n; ++i) {
        while (!s.empty() && h[s.top()] <= h[i]) {
            s.pop();
        }
        if (!s.empty()) {
            left_taller[i] = s.top();
        }
        s.push(i);
    }

    vector<long long> v(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        int l_idx = left_taller[i];
        v[i] = v[l_idx] + h[i] * (i - l_idx);
    }

    for (int i = 1; i <= n; ++i) {
        cout << v[i] + 1 << (i == n ? "" : " ");
    }
    cout << endl;

    return 0;
}
import java.util.Scanner;
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long[] h = new long[n + 1];
        for (int i = 1; i <= n; i++) {
            h[i] = sc.nextLong();
        }

        int[] leftTaller = new int[n + 1];
        Deque<Integer> s = new ArrayDeque<>();

        for (int i = 1; i <= n; i++) {
            while (!s.isEmpty() && h[s.peekLast()] <= h[i]) {
                s.pollLast();
            }
            if (!s.isEmpty()) {
                leftTaller[i] = s.peekLast();
            }
            s.offerLast(i);
        }

        long[] v = new long[n + 1];
        for (int i = 1; i <= n; i++) {
            int lIdx = leftTaller[i];
            v[i] = v[lIdx] + h[i] * (i - lIdx);
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= n; i++) {
            sb.append(v[i] + 1).append(i == n ? "" : " ");
        }
        System.out.println(sb.toString());
    }
}
import sys

def solve():
    n_str = sys.stdin.readline()
    if not n_str: return
    n = int(n_str)
    
    h_str = sys.stdin.readline()
    if not h_str: return
    h = [0] + list(map(int, h_str.split()))

    left_taller = [0] * (n + 1)
    s = []

    for i in range(1, n + 1):
        while s and h[s[-1]] <= h[i]:
            s.pop()
        if s:
            left_taller[i] = s[-1]
        s.append(i)

    v = [0] * (n + 1)
    for i in range(1, n + 1):
        l_idx = left_taller[i]
        v[i] = v[l_idx] + h[i] * (i - l_idx)

    ans = [val + 1 for val in v[1:]]
    print(*ans)

solve()

算法及复杂度

  • 算法: 单调栈、动态规划
  • 时间复杂度: 预处理 和计算 都只需要一次线性扫描,因此总时间复杂度为
  • 空间复杂度: 需要额外的数组来存储 ,以及一个单调栈,空间复杂度为
全部评论

相关推荐

04-16 10:27
已编辑
美团_Saas_后端开发
今天周一休息,突发奇想写一篇阶段总结。如题,我已经去了一个和Java彻底毫无关联的行业。曾经我以为自己能在计算机行业发光发热,拿到美团offer那会感觉自己天都亮了。没想到刚入行一年多就当了逃兵。从最开始的热爱到现在一看到代码就厌恶,不知道自己经历了什么。所以我去干什么了?答案是:在成都当了租房销售。上班那会压力大了就念叨着去干租房中介,但是一直下不去这个决心,想着自己学了四年多的计算机知识,终究还是不甘心。终于在某一天准备八股文的时候,看着无数篇和工作内容关系不大的理论知识,那一刻下定决心,决定尝试一下销售行业,也算是给自己一个交代。后面阴差阳错的投了成都自如去当租房管家,没想到面试很顺利,在当天一百多个面试的人里面,我成为了为数不多通过的几个幸运儿之一。目前已经培训通过,正式入职,也开了单,有压力但是每天过得很开心,真心喜欢那种和人交流的感觉,哪怕是最后没有选择找我租房。说这些也是想告诉那些大三,大四正在找Java实习而焦虑的同学:你们现在还年轻,选择很多,容错率也很高,可以尽情去尝试自己喜欢的行业和工作。不用因为某一次的面试没通过或者简历石沉大海而焦虑,更不用因为身边人都在挤编程的独木桥就强迫自己跟风。也算是自己的碎碎念吧,也希望自己能在新的领域取得一点小成就。也祝牛油工作顺利!
沉淀小子:干啥都不丢人啊,生存是必须要的,销售很考验一个人综合素质能力的,好的销售人脉和资源可不比写字楼的白领差啊
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务