算法竞赛进阶指南_李煜东_17588649_zhelper-search.pdf
索引:59
0x11 栈
基础应用
两个栈维护前面区间的最小元素
可以开个小根堆维护最小值,但是这样时间是logN的。如果只用一个变量来存,一旦出现了出栈操作就无从下手了。因此用一个线性结构来保存每个历史时刻的最小值。
开两个栈,一个用来存数据,一个记录当前最小值。
有数据入栈,就把这个数据与minn栈栈顶做对比决定是否入栈。出栈也做个对比即可维护这样一个数据结构。
class MinStack {
public:
/** initialize your data structure here. */
stack<int> st;
stack<int> minn;
MinStack() {
}
void push(int x) {
st.push(x);
if(minn.empty()||minn.top()>=x) minn.push(x);
}
void pop() {
if(st.top()==minn.top()) minn.pop();
st.pop();
}
int top() {
return st.top();
}
int getMin() {
return minn.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
序列中修改某个指定位置
始终在序列中间,某个指定位置进行修改,就可以用两个栈,类似于对顶堆用对顶栈来维护。
P2201 数列编辑器 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using ll=long long;
#define int ll
const int N=1e6+10;
const int M=1e5+10;
int q;
int s[N],m[N];
std::stack<int> l,r;
void solve()
{
std::cin>>q;
m[0]=-2e9;
while(q--)
{
char op;
std::cin>>op;
//getchar();
int x;
if(op=='I')
{
std::cin>>x;
l.push(x);
s[l.size()]=s[l.size()-1]+x;
m[l.size()]=std::max(m[l.size()-1],s[l.size()]);
}else if(op=='D'&&l.size()){
l.pop();
}else if(op=='L'&&l.size()){
r.push(l.top());
l.pop();
}else if(op=='R'&&r.size()){//光标右移
x=r.top();
r.pop();
l.push(x);
s[l.size()]=s[l.size()-1]+x;
m[l.size()]=std::max(m[l.size()-1],s[l.size()]);
}else if(op=='Q'){
std::cin>>x;
std::cout<<m[x]<<'\n';
}
}
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t=1;
//std::cin>>t;
while(t--)
{
solve();
}
return 0;
}
值得注意的是开了ios之后就不要用getchar()这类c的输入了。
入栈顺序一定,求可能的出栈情况(数学题,后面补)
dfs枚举O()
这题要求输出每种可能的出栈顺序,因为这里n<20。
dfs枚举,对于每一个状态,我们只有两种选择:
1入栈
2出栈
#include<bits/stdc++.h>
using ll=long long;
const int N=2e5+10;
int n;
std::vector<int> ans;
std::stack<int> st;
int cnt;
void dfs(int x)
{
if(cnt>=20) return ;
if(ans.size()>=n)//全都出栈了
{
cnt++;
for(auto i:ans) std::cout<<i;
std::cout<<'\n';
return ;
}
if(st.size())//如果栈里还有元素
{//考虑出栈
ans.push_back(st.top());
st.pop();
dfs(x);
st.push(ans.back());
ans.pop_back();
}
if(x<=n)//还能入栈
{
st.push(x);
dfs(x+1);
st.pop();
}
}
void solve()
{
std::cin>>n;
dfs(1);
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t=1;
//std::cin>>t;
while(t--)
{
solve();
}
return 0;
}
递推O(n)
这个问题只关心方案数并不关心具体的方案。数据是60000,暴力枚举肯定是不行的。
表达式求值
后缀表达式求值
1.如果遇到一个数就把这个数入栈
2.遇到运算符就弹出栈中两个元素进行运算,把结果入栈
3.扫描完成后,栈中只剩一个数,就是表达式的值
中缀表达式求值
如果想让计算机求解中缀表达式,最快的方法就是把中缀表达式转化成后缀表达式。这个转化过程可以用栈O(N)完成。
1.建立一个存运算符的栈,逐一扫描中缀表达式的元素。
1)遇到一个数就输出
2)左括号入栈
3)右括号,不断出栈并输出直到栈顶为左括号,然后左括号出栈。
4)遇到运算符,只要栈顶符号优先级不低于新符号就不断出栈,最后把新符号入栈。乘除>加减>左括号。
2.依次取出并输出栈中所有剩余符号,最终输出的序列就是一个与原中缀表达式等价的后缀表达式。
也可以递归直接求解,时间复杂度O(N)。
中缀表达式的递归法求值
求解中缀表达式s[1~N]的值
1.在L~R中考虑没有被任何括号包含的运算符
1)若存在加减号,选最后一个,分成左右两半递归,结果相加减,返回。
2)若存在乘除号,选最后一个,分成左右两半递归,结果相乘除,返回。
2.所有运算符都被括号包含
1)首尾字符是括号,递归求解S[L+1~R-1],把结果返回
2)否则,说明区间S[L~R]是一个数,直接返回数值
单调栈
HISTOGRA - Largest Rectangle in a Histogram - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
因为最大矩形只与高和宽有关系,因此我们可以对每个点,求出以它为高的最小左边界和最大右边界。那么这个问题就变成了,找出左边第一个比它小的坐标,右边第一个比它小的坐标。最后枚举每个点,计算面积枚举最大值即可。
找到左边第一个小/大,这是一个经典的单调栈问题。我们对左右两边各做一次就好。
#include<bits/stdc++.h>
using ll=long long;
const int N=1e5+10;
int n;
int h[N],l[N],r[N];
std::stack<int> sl,sr;
ll ans=-1;
void solve()
{
while(std::cin>>n&&n!=0)
{
for(int i=1;i<=n;i++) std::cin>>h[i];
h[0]=-1,h[n+1]=-1;
//找左边第一个小的
sl.push(0);//存下标
for(int i=1;i<=n;i++)
{
while(sl.size()&&h[sl.top()]>=h[i]) sl.pop();
l[i]=sl.top();
sl.push(i);
}
sr.push(n+1);
for(int i=n;i>=1;i--)
{
while(sr.size()&&h[sr.top()]>=h[i]) sr.pop();
r[i]=sr.top();
sr.push(i);
}
ans=0;
for(int i=1;i<=n;i++)
{
ans=std::max(ans,(ll)h[i]*(r[i]-l[i]-1));
}
std::cout<<ans<<'\n';
}
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t=1;
//std::cin>>t;
while(t--)
{
solve();
}
return 0;
}
练习
给定入栈顺序,判定出栈顺序是否合法
LCR 148. 验证图书取出顺序 - 力扣(LeetCode)这两题完全一样。
对每个应该入栈的元素入栈,同时比较栈顶元素和当前应该出栈的元素是否相同,相同就出栈。如果最后栈空说明可以,不空就不行。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
std::stack<int> st;
int j=0;
for(auto i:pushed)
{
st.push(i);
while(st.size()&&st.top()==popped[j])
{
st.pop();
j++;
}
}
if(st.size()) return false;
else return true;
}
};
给定入栈顺序,求出字典序最大的出栈顺序
A-栈和排序_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)
思想就是比较当前栈顶元素比后面待入栈的元素都要大了,就要输出,否则我们就可以等到后面入栈的时候再输出。
#include<bits/stdc++.h>
using ll=long long;
const int N=1e6+10;
int n;
int a[N],m[N];
std::stack<int> st;
void solve()
{
int n;
std::cin>>n;
for(int i=1;i<=n;i++)
{
std::cin>>a[i];
}
//求出后缀最大值
for(int i=n;i>=1;i--)
{
m[i]=std::max(m[i+1],a[i]);
}
for(int i=1;i<=n;i++)
{
st.push(a[i]);
while(st.size()&&st.top()>m[i+1])
{
std::cout<<st.top()<<" ";
st.pop();
}
}
while(st.size())
{
std::cout<<st.top()<<" ";
st.pop();
}
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t=1;
//std::cin>>t;
while(t--)
{
solve();
}
return 0;
}
给定后缀表达式计算结果
B-牛牛与后缀表达式_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给定一个后缀表达式,返回它的结果
* @param str string字符串
* @return long长整型
*/
stack<long long> st;
long long legalExp(string str) {
// write code here
int flag=0;
long long num=0;
for(auto i:str)
{
if(i>='0'&&i<='9')
{
num=num*10+i-'0';
}else if(i=='#'){
st.push(num);
num=0;
}else{//运算符
long long a=st.top();
st.pop();
long long b=st.top();
st.pop();
if(i=='+') num=a+b;
else if(i=='-') num=b-a;
else num=a*b;
st.push(num);
num=0;
}
}
return st.top();
}
};
判断一个字符串能否由“ab“不停插入得到
C-好串_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)
类似于括号匹配问题,可以把a看作左括号,b看作右括号。每遇到一个b就去弹出一个a。
#include<bits/stdc++.h>
std::string s;
std::stack<char> st;
void solve()
{
std::cin>>s;
for(auto i:s)
{
if(i=='a') st.push(i);
else{
if(st.empty()||st.top()!='a')
{
std::cout<<"Bad"<<'\n';
return ;
}
st.pop();
}
}
if(st.size()) std::cout<<"Bad"<<'\n';
else std::cout<<"Good"<<'\n';
}
signed main()
{
int t=1;
//std::cin>>t;
while(t--)
{
solve();
}
return 0;
}