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 可能导致的超时问题
相关推荐
楼田莉子1 小时前
C++20新特性:协程
开发语言·c++·后端·学习·c++20
炘爚1 小时前
phase1:基础框架——编译 + MySQL + 登录/注册
linux·c++
水木流年追梦2 小时前
大模型入门-大模型优化方法13- MTP 多 token 输出、DCA 双块注意力
人工智能·分布式·算法·正则表达式·prompt
特种加菲猫2 小时前
C++11核心特性深度解析:从列表初始化到lambda与包装器
开发语言·c++
数据皮皮侠2 小时前
全国消协智慧 315 平台投诉信息数据库
大数据·人工智能·算法·百度·制造
8Qi82 小时前
LeetCode 115 & 392:不同子序列 / 判断子序列
算法·leetcode·职场和发展·动态规划
枕星而眠2 小时前
C++ 面向对象核心机制深度解析:多态性、虚函数、虚继承与 final 类
运维·开发语言·c++·后端
小蒋学算法2 小时前
算法-乘法表中第K小的数-二分
数据结构·算法
智者知已应修善业2 小时前
【51单片机8个LED,已经使用了D1D2,怎么样在不动D1D2的前提下实现D6~D8的流水灯】2024-1-19
c++·经验分享·笔记·算法·51单片机