栈
经典题
for循环让数字按顺序压入栈,然后里面while循环判断是否弹出栈,最后循环结束的时候看看栈是否为空,即该出栈顺序有没有问题。
#include<bits/stdc++.h>
using namespace std;
int a[1010];
int main()
{
int n;
cin>>n;
stack<int>st;
int t;
cin>>t;
while(t--)
{
for(int i=1;i<=n;i++) cin>>a[i];
int pos=1;
for(int i=1;i<=n;i++)
{
st.push(i);
while(!st.empty()&&st.top()==a[pos])
{
st.pop();
pos++;
}
}
if(st.empty()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
例题1:
给你一个1->n的排列和一个栈,入栈顺序给定
你要在不打乱入栈顺序的情况下,对数组进行从大到小排序
当无法完全排序时,请输出字典序最大的出栈序列
思路:观察发现,当一个数后面没有比他大的数的时候他必出栈
#include<bits/stdc++.h>
using namespace std;
int a[1000010];
int maxn[1000010];
int main()
{
int n;
stack<int>st;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=n;i>=1;i--) maxn[i]=max(maxn[i+1],a[i]);
for(int i=1;i<=n;i++)
{
st.push(a[i]);
while(!st.empty()&&st.top()>maxn[i+1])
{
cout<<st.top()<<' ';
st.pop();
}
}
while(!st.empty())
{
cout<<st.top()<<' ';
st.pop();
}
}
水题:
牛牛喜欢跟字符串玩耍,他刚刚学会了一个新操作,将一个字符串x插入另一个字符串y中(包括放在开头和结尾)
牛牛认为如果一个串是好的当这个串能按照如下方法被构造出来:
一开始,有一个空串,然后执行0次或者若干次操作,每次操作将ab插入当前的字符串
根据上面的定义,ab, aabb, aababb都是好串,aab,ba,abbb并不是好串
现在给你一个字符串s,判断s是否是好串
思路:对于a让他入栈,对于b,出一个a,如果此时栈为空则不是好串,最后遍历完看栈是否为空,不空不是好串。
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
int len=s.size();
stack<char> st;
for(int i=0;i<len;i++)
{
if(s[i]=='a') st.push(s[i]);
else {
if(st.empty())
{
cout<<"Bad"<<'\n';
return 0;
}else{
st.pop();
}
}
}
if(st.empty()) cout<<"Good"<<'\n';
else cout<<"Bad"<<'\n';
return 0;
}
解法2:考虑到栈中只会有a,所以没必要用栈维护,只需要将a全部转化为1,然后b转化为-1,中途前缀和为负数直接不是好串,最后遍历完和为0是好串。
队列
例1:
合并果子
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
思路:由于合并后的果子先后顺序一定是从小到大的,所以可以把合并后的果子放在另一个队列里,这样这个队列就自然是从小到大排序,然后每次询问两个队列的首元素谁小就拿谁出来。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
queue<int> q1,q2;
int a[100010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) q1.push(a[i]);
ll ans=0;
for(int i=1;i<=n-1;i++)//一共合并n-1次
{
int x[3];
for(int j=1;j<=2;j++)
{
if(q2.empty()||!q1.empty()&&q1.front()<q2.front())
{
x[j]=q1.front();
q1.pop();
}else{
x[j]=q2.front();
q2.pop();
}
}
ans+=x[1]+x[2];
q2.push(x[1]+x[2]);
}
cout<<ans;
} #include<bits/stdc++.h>
using namespace std;
#define ll long long
queue<int> q1,q2;
int a[100010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) q1.push(a[i]);
ll ans=0;
for(int i=1;i<=n-1;i++)//一共合并n-1次
{
int x[3];
for(int j=1;j<=2;j++)
{
if(q2.empty()||!q1.empty()&&q1.front()<q2.front())
{
x[j]=q1.front();
q1.pop();
}else{
x[j]=q2.front();
q2.pop();
}
}
ans+=x[1]+x[2];
q2.push(x[1]+x[2]);
}
cout<<ans;
}
deque
例题:
思路:for循环遍历数组每一个数,让它进入maxn(minn)队列,比如最大值,如果新进入的值大于前面队列中有的值,则前面队列中比该数小的数都不可能成为最大值,因为有它在,最小值同理,注意进入队列的是数组下标,而且队列中的元素的值都是从大到小的(因为有“淘汰”),当i遍历到队列首元素的后面时,首元素出队。
#include<bits/stdc++.h>
using namespace std;
deque<int> maxn,minn;
int a[1000010];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
maxn.push_back(1);
minn.push_back(1);
for(int i=2;i<=n;i++)
{
if(i-k>=minn.front()) minn.pop_front();
if(a[i]<a[minn.back()])
{
while(a[i]<a[minn.back()]&&!minn.empty())
{
minn.pop_back();
if(minn.empty()) break;
}
}
minn.push_back(i);
if(i>=k)
{
cout<<a[minn.front()]<<' ';
}
}
cout<<'\n';
for(int i=2;i<=n;i++)
{
if(i-k>=maxn.front()) maxn.pop_front();
if(a[i]>a[maxn.back()])
{
while(a[i]>a[maxn.back()]&&!maxn.empty())
{
maxn.pop_back();
if(maxn.empty()) break;
}
}
maxn.push_back(i);
if(i>=k)
{
cout<<a[maxn.front()]<<' ';
}
}
}
变式:
给定一个数组,求数组中每个元素右边第一个比它大的元素。
思路:跟滑动窗口一样的题,用一个deque来维护可能被仰望的牛,for循环从后往前,如果遍历到当前的牛比deque中最左边的牛高,那最左边这头牛不可能成为被仰望的牛了,就一直这样循环淘汰下去,然后队列里第一头牛就是遍历到的当前牛的仰望对象。
#include<bits/stdc++.h>
using namespace std;
deque<int> q;
int a[100010];
int ans[100010];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
q.push_front(n);
for(int i=n;i>=1;i--)
{
while(!q.empty()&&a[i]>=a[q.front()])
{
q.pop_front();
}
if(!q.empty())
{
ans[i]=q.front();
}else{
ans[i]=0;
}
q.push_front(i);
}
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}
可以发现前两道题队列中的元素都是单调的(淘汰机制),所以称该题型为单调队列,单调栈
Plus版:
思路:对于每一个块,我们找到左边第一个比它小的块和右边第一个比它小的块,然后它能扩展到最大的矩形面积就是(i-zm[i]+ym[i]-i-1)*a[i],然后这种左边第一个某某,右边第一个某某就是上面两个例题的做法了。
#include<bits/stdc++.h>
using namespace std;
deque<int>q1,q2;
long long a[100010];
int zm[100010];
int ym[100010];
int main()
{
int n;
while(cin>>n)
{
if(n==0) break;
while(!q1.empty()) q1.pop_back();
while(!q2.empty()) q2.pop_back();
for(int i=1;i<=n;i++) cin>>a[i];
q1.push_back(1);
zm[1]=0;
ym[n]=n+1;
q2.push_front(n);
for(int i=2;i<=n;i++)
{
while(!q1.empty()&&a[q1.back()]>=a[i])
{
q1.pop_back();
}
if(!q1.empty()) zm[i]=q1.back();
else zm[i]=0;
q1.push_back(i);
}
for(int i=n-1;i>=1;i--)
{
while(!q2.empty()&&a[i]<=a[q2.front()])
{
q2.pop_front();
}
if(!q2.empty()) ym[i]=q2.front();
else ym[i]=n+1;
q2.push_front(i);
}
long long maxn=-1;
for(int i=1;i<=n;i++)
{
maxn=max(maxn,(i-zm[i]+ym[i]-i-1)*a[i]);
}
cout<<maxn<<endl;
}
}
单调栈单调队列可以用来求左边(右边)第一个某某。
然而这种求法都是O(n)复杂度的,因为每个元素只有一次进出队列的机会。