牛客NC14893 栈和排序(贪心 + 栈 + 后缀最大值维护)
题目描述
给定一个从 111 到 nnn 的排列 PPP,以及一个空栈。你需要按顺序将排列中的元素依次入栈。在任何时刻,你都可以选择将栈顶元素出栈并将其加入输出序列(前提是栈非空)。入栈顺序不可改变。
你的目标是通过合法的出栈顺序,尽可能使输出序列为一个严格递减序列:n,n−1,...,1n, n-1, ..., 1n,n−1,...,1。
然而,由于栈的操作限制,这并不总是可能实现。因此,请你输出字典序最大的合法出栈序列。
输入格式
第一行一个整数 nnn,表示排列的大小。(1≤n≤106)(1 \leq n \leq 10^6)(1≤n≤106)
第二行 nnn 个两两不同的正整数,表示排列 PPP,是 1∼n1 \sim n1∼n 的一个排列。
输出格式
输出一行若干个整数,为最终的出栈序列(以空格分隔),结尾不能有多余空格。
输入样例
5
2 1 5 3 4
输出样例
5 4 3 1 2
样例解释
入栈顺序和出栈过程如下:
2 入栈
1 入栈
5 入栈
5 出栈 → 输出 5
3 入栈
4 入栈
4 出栈 → 输出 4
3 出栈 → 输出 3
1 出栈 → 输出 1
2 出栈 → 输出 2
最终得到的合法出栈序列为:5 4 3 1 2
,是所有合法出栈方案中字典序最大的。
说明/提示
-
1≤n≤1061 \leq n \leq 10^61≤n≤106
-
保证输入的排列是 1∼n1 \sim n1∼n 的一个合法全排列。
提交链接
栈和排序
思路分析
💡 解题思路:贪心
我们不能随意调整入栈顺序(只能按给定排列顺序依次压入),但可以控制什么时候出栈。
要想得到字典序最大的出栈序列,本质上就是要尽量让大的数先出现在结果序列的前面。
因此我们采用如下贪心策略:
🎯 贪心
-
维护一个变量 ididid,表示当前还未出现在输出序列中的最大值,初始为 nnn。
-
遍历输入序列:
-
如果当前元素等于 ididid,说明它已经是当前剩下元素中最大的,应该立即输出,并将 id−−id--id−−。
-
否则暂时入栈,等待后续时机。
-
-
输入序列遍历完后,将栈中的元素按栈顶到栈底的顺序依次输出(即后进先出)。
这个策略的关键:尽可能早地把大的数输出,小的数先“藏起来”放在栈中等待
参考代码
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, id;cin >> n;stack<int> s;id = n; // 从n开始出栈,保证字典序最大// n 个数字依次入栈for (int i = 1; i <= n; i++){int x;cin >> x;if (x == id){cout << x << " ";id--;}else{s.push(x);}}while (!s.empty()){cout << s.top() << " ";s.pop();}return 0;
}
💡 解题思路:后缀最大值维护
我们要在遵循栈操作规则的前提下,构造出 字典序最大的出栈序列。
🔍 核心观察
每次只能将栈顶元素输出。
元素必须按输入顺序依次入栈。
想要字典序最大,就应该尽量先输出大的数。
✅ 贪心策略
-
从左到右遍历输入序列,维护一个栈:每次将当前元素 P[i]P[i]P[i] 入栈。
-
不断检查当前栈顶是否是“可以放心输出”的最大值。即:当前栈顶元素大于或等于后面所有还未入栈的元素。换句话说,如果后面不会再出现比它更大的数,就可以把它输出。
为了快速判断后面是否还有比栈顶更大的数,我们可以提前预处理出每个位置后缀最大值 sufMax[i]
。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main()
{int n;cin >> n;vector<int>p(n);for(auto &it : p)cin >> it;vector<int>sufmax(n + 1); //维护i~n-1的最大值for(int i = n - 1; i >= 0; i--){sufmax[i] = max(sufmax[i + 1] , p[i]);}stack<int>sk;for(int i = 0 ; i < n; i++){sk.push(p[i]);while(!sk.empty() && sk.top() > sufmax[i + 1]){cout << sk.top() << " ";sk.pop();}}return 0;
}