P2947 [USACO09MAR] Look Up S

记录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)。

  1. 逆序遍历:因为奶牛是"向右看齐",寻找右侧第一个比自己高的奶牛,所以从右向左逆序遍历数组最为直观。
  2. 单调递减栈 :我们在遍历过程中维护一个栈,栈中存放奶牛的编号,且这些奶牛的身高从栈底到栈顶是严格单调递减的。
  3. 状态转移与剔除:当遍历到奶牛 i 时,如果栈顶奶牛的身高 ≤ 奶牛 i 的身高,说明栈顶奶牛被奶牛 i "挡住"了,它永远不可能成为奶牛 i 左侧任何奶牛的仰望对象,因此直接将其弹出栈。
  4. 确定答案:弹出所有"不够高"的奶牛后,如果栈不为空,此时的栈顶元素就是奶牛 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 可能导致的超时问题
相关推荐
罗西的思考4 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营7 小时前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队7 小时前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户8055336980314 小时前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC1 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC1 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK1 天前
线段树维护区间 k 次方和
c++·数学·算法·stl
卷无止境2 天前
Eigen 库如何借助 OpenMP 加速计算
c++·后端
_清歌2 天前
DSpark 深度解读:DeepSeek-V4 如何用「半自回归」把推理速度提升 85%
算法