洛谷P5788 【模板】单调栈——单调栈

题目来源

https://www.luogu.com.cn/problem/P5788

题目背景

模板题,无背景。

题目描述

给出项数为 n 的整数数列 a1...n

定义函数 f(i) 代表数列中第 i 个元素之后第一个大于 ai的元素的下标,即 f(i)=min

i<j≤n,aj>ai{j}。若不存在,则 f(i)=0。

试求出 f(1...n)。

输入格式

第一行一个正整数 n。

第二行 n 个正整数 a1...n

输出格式

一行 n 个整数表示 f(1),f(2),...,f(n) 的值。

输入输出样例

输入 #1

5

1 4 2 3 5

输出 #1

2 5 4 5 0
说明/提示

【数据规模与约定】

对于 30% 的数据,n≤100;

对于 60% 的数据,n≤5×10^3;

对于 100% 的数据,1≤n≤3×10^6,1≤ai≤10^9。

算法分析

一、题目背景与核心需求​

首先,我们来明确题目要求,这是解题的第一步。洛谷 P5788 的题目描述如下:​

给定一个长度为 n

的数组

​

a

,请你找到每个元素右边第一个比它大的元素的值,如果不存在则输出

​

0

。​

输入格式:第一行输入一个整数

​

n

(

​

1≤n≤3×10

6

),第二行输入

​

n

个整数,表示数组

​

a

。​

输出格式:输出一行,包含

​

n

个整数,依次表示每个元素右边第一个比它大的元素的值。​

​

从题目中我们能提炼出两个关键信息:​

问题本质:对于数组中的每个元素

​

ai

,寻找其右侧第一个大于

​

ai

的元素,记为

​

ansi

;若没有,则

​

ansi=0

。​

数据规模:

​

n

最大可达

​

3×10

6

,这意味着我们必须设计时间复杂度为

​

O(n)

的算法 ------ 如果使用暴力枚举(对每个元素遍历其右侧所有元素),时间复杂度为

​

O(n

2

)

,在

​

n=3e6

时会严重超时,因此需要更高效的解法,而单调栈正是解决这类问题的 "利器"。​

二、单调栈原理:什么是单调栈?​

在讲解解题思路前,我们先明确 "单调栈" 的定义。单调栈是一种特殊的栈结构,其核心特性是:栈内的元素始终保持严格的单调性(递增或递减)。根据单调性的不同,单调栈可分为 "单调递增栈" 和 "单调递减栈":​

单调递增栈:栈内元素从栈底到栈顶依次递增(即每次入栈的元素必须大于栈顶元素,若不满足则弹出栈顶元素,直到满足条件或栈为空)。​

单调递减栈:栈内元素从栈底到栈顶依次递减(同理,每次入栈的元素必须小于栈顶元素,否则弹出栈顶元素)。​

单调栈的优势在于,它能在线性时间内处理数组中 "Next Greater Element""Next Smaller Element" 等问题,其本质是通过 "维护栈的单调性",避免了对元素的重复遍历,将暴力解法的

​

O(n

2

)

时间复杂度优化到

​

O(n)

。​

三、解题思路:如何用单调栈解决本题?​

回到洛谷 P5788,我们需要寻找每个元素 "右边第一个更大的元素",此时应选择单调递减栈,原因如下:​

单调递减栈中,栈顶元素是当前未找到 "右边更大元素" 的元素中最小的一个。当遇到一个新元素

​

ai

时,如果

​

ai

大于栈顶元素,说明

​

ai

就是栈顶元素的 "右边第一个更大元素";此时弹出栈顶元素,并记录答案,直到栈顶元素大于

​

ai

或栈为空,再将

​

ai

入栈。​

这样一来,每个元素只会入栈一次、出栈一次,整个过程的时间复杂度为

​

O(n)

,完全能满足

​

n=3e6

的数据规模要求。​

具体解题步骤可拆解为以下四步:​

初始化:创建一个空栈(用于存储未找到 "右边更大元素" 的元素下标,而非元素值,这样便于后续记录答案),创建一个长度为

​

n

的答案数组

​

ans

,初始值均为

​

0

(对应 "没有更大元素" 的情况)。​

遍历数组:从数组的第一个元素到最后一个元素,依次处理每个元素

​

ai

(

​

i

为当前元素下标)。​

维护栈的单调性:​

当栈不为空,且当前元素

​

ai

大于栈顶下标对应的元素

​

astack.top()

时:​

弹出栈顶下标

​

top

,此时

​

ai

就是

​

atop

的 "右边第一个更大元素",因此令

​

anstop=ai

。​

重复此过程,直到栈为空或

​

ai≤astack.top()

。​

将当前元素的下标

​

i

入栈。​

输出答案:遍历结束后,答案数组

​

ans

中存储的就是每个元素对应的结果,直接输出即可。​

举个例子:直观理解过程​

为了让大家更清晰地掌握步骤,我们以一个小规模数组为例:

​

a=3,1,2,4

,

​

n=4

。​

初始化:栈为空,

​

ans=0,0,0,0

。​

处理

​

i=0

(

​

a0=3

):栈为空,直接入栈,栈:

​

0

。​

处理

​

i=1

(

​

a1=1

):

​

a1≤a0

(1≤3),栈保持递减,入栈,栈:

​

0,1

。​

处理

​

i=2

(

​

a2=2

):​

栈顶下标为 1,

​

a2=2>a1=1

,弹出 1,令

​

ans1=2

;​

此时栈顶下标为 0,

​

a2=2≤a0=3

,停止弹出,将 2 入栈,栈:

​

0,2

。​

处理

​

i=3

(

​

a3=4

):​

栈顶下标为 2,

​

a3=4>a2=2

,弹出 2,令

​

ans2=4

;​

栈顶下标为 0,

​

a3=4>a0=3

,弹出 0,令

​

ans0=4

;​

栈为空,将 3 入栈,栈:

​

3

。​

遍历结束:栈中剩余下标 3(

​

a3=4

),其右侧无元素,

​

ans3

保持 0。​

最终答案:

​

ans=4,2,4,0

,与预期结果一致。​

四、代码实现:从基础版到优化版​

理解了思路后,我们来编写代码。需要注意的是,由于

​

n

最大为

​

3e6

,在 C++ 中需注意输入输出效率(使用 scanf/printf 而非 cin/cout,否则可能超时),同时栈的实现可直接使用 STL 的 stack,但为了进一步提升效率,也可使用数组模拟栈(减少 STL 的开销)。

Code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
 
const int maxn=3e6+5;
int idx[maxn], a[maxn];
int stk[maxn];
int n,top;
 
int main() {
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    for(int i=n;i>=1;i--) {
        while(top && a[stk[top]]<=a[i]) {
            top--;
        }
        if(top)idx[i]=stk[top];
        else idx[i]=0;
        stk[++top]=i;
    }
    for(int i=1; i<=n; i++) cout<<idx[i]<<" ";
 
    return 0;
}
相关推荐
Doubbbbbbble云几秒前
图算法中的剪枝技术与启发式优化分析4
算法·机器学习·剪枝
鶴哥只手遮天3 分钟前
CFL3D深度解析:NASA经典CFD求解器的架构、算法与工程实践
算法
维克兜率天5 分钟前
【维克】价差交易实战:配对交易的核心参数设置
大数据·笔记·算法·量化
dtq04245 分钟前
数据结构 - 树的概念
c语言·数据结构·学习·算法
晒太羊的猫6 分钟前
hot100多维动态规划刷题
算法·动态规划
Dream it possible!10 分钟前
LeetCode_热题100_知识点总结
算法·leetcode·职场和发展
鶴哥只手遮天11 分钟前
从像素到战场:红外场景仿真引擎的核心技术深度解析
算法
anew___13 分钟前
《从零手写操作系统 (19):Ext2文件系统实战——从内存到磁盘的跨越》
java·服务器·前端·数据库·算法
盘古开天166616 分钟前
PPO算法原理详解(上):从策略梯度到近端策略优化的演进之路
人工智能·算法·机器学习·强化学习·ppo
云栈开源日记17 分钟前
机器人运动规划从A*到Minimum Snap四大算法全解析
算法·ai·机器人