洛谷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

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

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

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

a[i]

,寻找其右侧第一个大于

a[i]

的元素,记为

ans[i]

;若没有,则

ans[i]=0

。​

数据规模:

n

最大可达

3×10

6

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

O(n)

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

O(n

2

)

,在

n=3e6

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

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

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

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

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

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

O(n

2

)

时间复杂度优化到

O(n)

。​

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

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

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

a[i]

时,如果

a[i]

大于栈顶元素,说明

a[i]

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

a[i]

或栈为空,再将

a[i]

入栈。​

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

O(n)

,完全能满足

n=3e6

的数据规模要求。​

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

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

n

的答案数组

ans

,初始值均为

0

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

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

a[i]

i

为当前元素下标)。​

维护栈的单调性:​

当栈不为空,且当前元素

a[i]

大于栈顶下标对应的元素

a[stack.top()]

时:​

弹出栈顶下标

top

,此时

a[i]

就是

a[top]

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

ans[top]=a[i]

。​

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

a[i]≤a[stack.top()]

。​

将当前元素的下标

i

入栈。​

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

ans

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

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

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

a=[3,1,2,4]

n=4

。​

初始化:栈为空,

ans=[0,0,0,0]

。​

处理

i=0

a[0]=3

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

0

。​

处理

i=1

a[1]=1

):

a[1]≤a[0]

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

0,1

。​

处理

i=2

a[2]=2

):​

栈顶下标为 1,

a[2]=2>a[1]=1

,弹出 1,令

ans[1]=2

;​

此时栈顶下标为 0,

a[2]=2≤a[0]=3

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

0,2

。​

处理

i=3

a[3]=4

):​

栈顶下标为 2,

a[3]=4>a[2]=2

,弹出 2,令

ans[2]=4

;​

栈顶下标为 0,

a[3]=4>a[0]=3

,弹出 0,令

ans[0]=4

;​

栈为空,将 3 入栈,栈:

3

。​

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

a[3]=4

),其右侧无元素,

ans[3]

保持 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;
}
相关推荐
moonsims3 小时前
Z3 Technology-适用于无人机和机器人的 4K 高清摄像机和视频编码器
算法
摘星编程3 小时前
深入浅出 Tokio 源码:掌握 Rust 异步编程的底层逻辑
网络·算法·rust·系统编程·tokio
泡沫冰@3 小时前
数据结构(9)
数据结构
Hard_Liquor4 小时前
Datawhale秋训营-“大运河杯”数据开发应用创新大赛
人工智能·深度学习·算法
liu****4 小时前
笔试强训(八)
开发语言·算法·1024程序员节
草莓工作室4 小时前
数据结构14:查找
数据结构·算法
逐步前行4 小时前
C数据结构--线性表(顺序表|单链表|双向链表)
c语言·数据结构·链表
草莓工作室5 小时前
数据结构13:排序
c语言·数据结构·排序算法
屈冠成5 小时前
C语言数组:编辑世界的坚固桥梁
c语言·开发语言·算法