记录125
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;// 定义常量 N,用于初始化数组大小,防止越界(题目数据规模 N ≤ 10^5)
int h[N],ans[N];// h用来存储每头奶牛身高的数组(下标从1开始),ans用来存储每头奶牛的"最近仰望对象"的编号
stack<int> st;// 单调栈,栈中存放的是奶牛的编号(下标)
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
// 核心逻辑:从右向左逆序遍历每一头奶牛
for(int i=n;i>=1;i--){
// 维护单调栈:如果栈不为空,且当前奶牛 i 的身高 >= 栈顶奶牛的身高
// 说明栈顶那头奶牛被挡住了(比当前矮或一样高),不可能成为 i 左边奶牛的仰望对象
// 因此将栈顶元素弹出,直到栈为空或者找到比当前奶牛更高的牛
while(!st.empty()&&h[st.top()]<=h[i]) st.pop();
if(st.empty()) ans[i]=0;// 如果栈空了,说明右边没有比当前奶牛更高的,仰望对象为 0
else ans[i]=st.top();// 如果栈不为空,此时的栈顶元素就是右边第一个比当前奶牛高的牛的编号
st.push(i);// 将当前奶牛的编号压入栈中,作为它左边奶牛潜在的"仰望对象"
}
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
return 0;// 结束程序
}
题目传送门
https://www.luogu.com.cn/problem/P2947
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是经典的**"下一个更大元素"(Next Greater Element)** 问题。如果采用双重循环暴力求解,时间复杂度会达到 O(N2) ,在 N≤10^5 的数据规模下必然超时。因此,我们需要使用单调栈这一数据结构,将时间复杂度优化到 O(N)。
- 逆序遍历:因为奶牛是"向右看齐",寻找右侧第一个比自己高的奶牛,所以从右向左逆序遍历数组最为直观。
- 单调递减栈 :我们在遍历过程中维护一个栈,栈中存放奶牛的编号,且这些奶牛的身高从栈底到栈顶是严格单调递减的。
- 状态转移与剔除:当遍历到奶牛 i 时,如果栈顶奶牛的身高 ≤ 奶牛 i 的身高,说明栈顶奶牛被奶牛 i "挡住"了,它永远不可能成为奶牛 i 左侧任何奶牛的仰望对象,因此直接将其弹出栈。
- 确定答案:弹出所有"不够高"的奶牛后,如果栈不为空,此时的栈顶元素就是奶牛 i 右侧第一个比它高的奶牛;如果栈为空,说明右侧没有更高的奶牛,答案为 0。最后将奶牛 i 入栈,作为更左侧奶牛的候选仰望对象。
代码分块详细解释
1. 头文件、常量定义与全局变量
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10; // 定义常量 N,用于初始化数组大小,防止越界(题目数据规模 N ≤ 10^5)
int h[N],ans[N]; // h 存储每头奶牛身高(下标从1开始),ans 存储每头奶牛的"最近仰望对象"编号
stack<int> st; // 单调栈,栈中存放的是奶牛的编号(下标)
- 作用 :完成基础数据结构的定义。使用全局数组和 STL
stack,避免在main函数中开辟过大的局部数组导致栈溢出。
2. 输入与初始化
cpp
int main(){
ios::sync_with_stdio(false);
cin.tie(0); // 关闭输入输出同步,提升读取速度
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i]; // 读入每头奶牛的身高
- 作用:快速读入数据规模 N 及每头奶牛的具体身高。
3. 核心逻辑:单调栈维护与逆序遍历
cpp
// 核心逻辑:从右向左逆序遍历每一头奶牛
for(int i=n;i>=1;i--){
// 维护单调性:如果栈不为空,且栈顶奶牛身高 <= 当前奶牛身高
// 说明栈顶奶牛被当前奶牛"挡住",不可能成为左边奶牛的仰望对象,将其弹出
while(!st.empty()&&h[st.top()]<=h[i]) st.pop();
// 确定当前奶牛的仰望对象
if(st.empty()) ans[i]=0; // 栈空,说明右边没有比当前奶牛更高的牛
else ans[i]=st.top(); // 栈非空,栈顶元素即为右侧第一个比它高的牛的编号
// 将当前奶牛入栈,作为它左边奶牛潜在的"仰望对象"
st.push(i);
}
- 作用 :这是整个算法的核心。通过
while循环剔除无用元素,保证栈内元素的单调递减性;通过判断栈是否为空得出当前奶牛的答案;最后将当前奶牛压入栈中。每个元素最多入栈和出栈一次,保证了 O(N) 的时间复杂度。
4. 输出结果
cpp
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n"; // 按顺序输出每头奶牛的仰望对象
return 0; // 结束程序
}
- 作用 :将预处理好的答案数组按题目要求的顺序逐行输出。使用
\n代替endl可以避免频繁刷新缓冲区,进一步提升输出效率。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 逆序遍历 | for(int i=n;i>=1;i--) |
从右向左扫描数组 | 契合"向右看齐"的题意,方便寻找右侧第一个更大元素 |
| 单调栈剔除 | while(... h[st.top()]<=h[i]) st.pop() |
弹出所有比当前矮或等高的元素 | 剔除被"挡住"的无效元素,维持栈的单调递减性 |
| 状态查询 | st.empty() ? 0 : st.top() |
获取栈顶元素或返回0 | 以 O(1)的时间复杂度直接获取右侧第一个更大元素的编号 |
| 候选入栈 | st.push(i) |
将当前元素下标压入栈中 | 将当前元素转化为左侧元素的潜在仰望对象 |
| IO优化 | ios::sync_with_stdio(false) |
关闭C/C++标准流同步 | 解决 10^5 级别数据量下 cin/cout 可能导致的超时问题 |