堆栈队列单调栈等
知识点
1.单调队列配合数组下标可以用来维护区间的最大值和最小值
只要在队列中构造非递增序列或非递减序列即可。具体操作就是及时弹出不合适的队尾,保证序列符合趋势,队头就是所要的答案。
2.栈存数取出来做减法时,注意后取的这个数才是作为被减数。
3.deque也可以实现reverse操作,也可以用下标访问每一个元素,也可以sort
题解
栈和排序
1.这一题其实只麻烦在,你把一个数入栈以后,万一它被下一个数堵住,这个数该怎么排在合适的位置
2.所以我们采取的策略是,开一个数组rm来记录,rm【i】代表i-n的区间的最大值,那么这个数组可以帮我们判断,当一个数处于栈顶的时候应该要取出来还是要放着,当这个栈顶的数没有比rm【i+1】大时就放着,如果有就取出来
3.所以完整的步骤是,我们记录完rm以后,遍历输入的数,如果这个数是当前区间的最大值就取出来,因为我们rm数组的范围是从大到小的,所以依次取出来的最大值一定是合法的
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
int n; cin>>n;
stack<int>st,ans;
vector<int>ve(n),rm(n+1);
for(int i=0;i<n;i++) cin>>ve[i];
for(int i=n-1;i>=0;i--) rm[i]=max(rm[i+1],ve[i]);
for(int i=0;i<n;i++)
{
st.push(ve[i]);
if(st.top()==rm[i])
{
ans.push(st.top());
st.pop();
while(!st.empty()&&st.top()>rm[i+1])
{
//判断栈顶这个数有没有比后面还没放的数大,如果有要取出来
ans.push(st.top());
st.pop();
}
}
}
//傻瓜题还卡格式
stack<int>res;
//放进res把顺序弄正了
while(!ans.empty())
{
res.push(ans.top());
ans.pop();
}
cout<<res.top();
res.pop();
while(!res.empty())
{
cout<<" "<<res.top();
res.pop();
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
牛牛与后缀表达式
1.直接从左往右遍历字符,开一个temp,记录数字,遇到#则把这个数放进去,然后temp变为0。
2.因为一定会保证栈里有两个值,所有遇到符号把两个元素取出来计算即可
3.但是注意遇到减号,应该是第二个元素-第一个元素,后取出来的做被减数
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给定一个后缀表达式,返回它的结果
* @param str string字符串
* @return long长整型
*/
public:
long long legalExp(string str) {
stack<long long>st;
long long temp=0;
for(int i=0;i<str.size();i++)
{
if(str[i]>='0'&&str[i]<='9')
{
temp=temp*10+str[i]-'0';
}else if(str[i]=='#'){
st.push(temp);
temp=0;
}else{
long long ans=0;
long long k1=st.top();
st.pop();
long long k2=st.top();
st.pop();
if(str[i]=='+') ans=k1+k2;
if(str[i]=='*') ans=k1*k2;
if(str[i]=='-') ans=k2-k1;//这里是k2-k1
st.push(ans);
}
}
return st.top();
}
};
好串
1.遇到a就让其入栈,遇到b就让其出栈,判断遇到b时栈里有没有a,或者遍历完以后栈是不是空的
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
string s;cin>>s;
int n=s.size();
stack<char>st;
for(int i=0;i<n;i++)
{
if(s[i]=='a') st.push(s[i]);
else {
if(st.empty()){
cout<<"Bad";
return ;
}
st.pop();
}
}
if(!st.empty()) cout<<"Bad";
else cout<<"Good";
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
吐泡泡
1.先把第一个字符放进去,然后从第二个开始遍历,每次放进去一个字符,把栈里能进行的反应全反应掉,你进去一个字符可能引起多次反应,比如栈里放oOo
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
string s;
while(cin>>s){
stack<char>st;
st.push(s[0]);
for(int i=1;i<s.size();i++)
{
st.push(s[i]);
//每一次放一个新的字符进去,应该把能反应的全反应掉
while(st.size()>1)
{
char a=st.top();
st.pop();
char b=st.top();
st.pop();
if(a==b)
{
if(a=='O') continue;
else st.push('O');
}else{
st.push(b);
st.push(a);
break;
}
}
}
stack<char>ans;
while(!st.empty())
{
ans.push(st.top());
st.pop();
}
while(!ans.empty()){
cout<<ans.top();
ans.pop();
}
cout<<endl;
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
Rails
1.真的纯傻瓜题,题意其实就是判断你入栈后出栈的顺序能不能符合1-n即可
2.纯卡格式
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
int n;
while(cin>>n&&n)
{
vector<int>ve(n+1);
int k;cin>>k;//这里输入第一次
if(k==0) cout<<endl;
//傻瓜题卡格式
while(k!=0){
stack<int>st;
ve[1]=k;
for(int i=2;i<=n;i++) cin>>ve[i];
int j=1;
for(int i=1;i<=n;i++){
st.push(i);
while(!st.empty()&&st.top()==ve[j])
{
st.pop();
j++;
}
}
if(st.empty()) cout<<"Yes";
else cout<<"No";
cout<<endl;
cin>>k;//这个作为下一次循环数组的ve【1】;
if(k==0) cout<<endl;//傻瓜题这还得有空格
}
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
Operating System
1.题意写的真晦涩,翻译一下就是,给你一个大小为n的盒子,然后你有m种球,询问次数为q,每一次都要检查一下盒子里有没有这种球,如果没有要增加一次次数,把这种球放进去。如果盒子里有这种球,查询到了就不增加次数。如果没有这种球,且盒子放满了这个球,你就要选择其中一种球拿出来,然后再把这种球放进去,增加一次次数,让你输出最少的次数能完成这些查询。
进行以下的分类讨论
情况a:盒子的最大容量大于你球的种类,那么直接输出询问次数里出现的球的种数。
情况b:盒子的最大容量小于球的种类,当你遍历询问序列的时候
1.先检查一下盒子里有无这种球,如果有,把对应存放球出现的队头删掉,不增加次数
2.如果没有,检查是否被装满,未被装满,直接放进去,次数+1
3.如果盒子已经被放满了,找到盒子里的数下一次不再出现的数,或最晚出现的把它删掉,然后放入当前这个球,次数++
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
int m,n,q;
while(cin>>n>>m>>q){
int ans=0;
queue<int>qe[m+1];//存放每一个页面编号出现的位置,记得是m+1,而不是n+1
vector<int>ask(q+1);//放询问的页面编号,是q+1哦
set<int>st;//统计出现的不同页面编号的数量
for(int i=1;i<=q;i++){
cin>>ask[i];
qe[ask[i]].push(i);
st.insert(ask[i]);
}
set<int>se;//内存,用来存页面
if(n>=m)//最大存储页面的数量大于询问中的页面最大编号
{
cout<<st.size()<<endl;
continue;
}
for(int i=1;i<=q;i++)//注意次数是q哦
{
int id=ask[i];
//检查内存里有没有这个页面,如果有删除对应队列的头,不增加次数
if(se.count(id)!=0) qe[id].pop();
else{//如果没有,检查一下是不是内存是不是够添加这个新的
if(se.size()<n){
se.insert(id);
qe[id].pop();//记得每一次操作完删除对应队列的编号
ans++;//内存里没有,放进去也是算一次置换的,所以也要加
}else{
//内存已经满了,我们先找后面不再出现数删掉。
int p=0,pos=-1;//p用来记录已经不再出现的数或最晚出现的数,pos用来找最晚出现的数的位置
for(auto t:se)
{
if(qe[t].empty()){
p=t;
break;
}
if(qe[t].front()>pos){
pos=qe[t].front();
p=t;
}
}
//找完以后在内存里把它删掉
se.erase(p);
se.insert(id);
qe[id].pop();
ans++;
}
}
}
cout<<ans<<endl;
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
[NOIP2004]合并果子
1.利用优先队列自动排序的特性,在队列里数量大于两个的时候,取出队头两个相加即可
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
void solve()
{
int n;cin>>n;
priority_queue<int,vector<int>,greater<int> >q;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
q.push(x);
}
int ans=0;
while(!q.empty())
{
int k1=0,k2=0;
k1=q.top();
q.pop();
//每次取完一个检查一下是不是还有
//没有就不取了,只剩一个值时,这个已经在上一次加过了
if(!q.empty())
{
k2=q.top();
q.pop();
q.push(k1+k2);
ans+=k1+k2;
}
//cout<<k1+k2<<endl;
}
cout<<ans;
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
滑动窗口
1.使用deque里存下标来维护,具体见代码详细注解
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
deque<int>q1,q2;
void solve()
{
int n,k; cin>>n>>k;
vector<int>ve(n+1);
for(int i=1;i<=n;i++) cin>>ve[i];
for(int i=1;i<=n;i++)//我们单调队列里放的是下标,因为可以帮助我们检查是否队头不在区间了
{
//如果当前队尾的下标对应的元素大于当前这个元素,我们就把队尾换掉
//因为我们要的是非递减序列,这样可以保证队头的值一定是最小的,每次只需要输出队头即可
while(!q1.empty()&&ve[q1.back()]>ve[i]) q1.pop_back();
//使用while循环来删,比如碰到了 1 2 3 下一个是-1则会把队列元素都删掉再放进-1的下标
//这就是保证队头最小的原因
q1.push_back(i);//放的是下标
while(!q1.empty()&&i-q1.front()>k-1) q1.pop_front();//检查队头是否还在这个区间内
if(i>=k) cout<<ve[q1.front()]<<" ";//题意
}
cout<<endl;
//同理,只是我们要的是非递增序列,因为这样保证队头的值是最大的。
for(int i=1;i<=n;i++)
{
while(!q2.empty()&&ve[q2.back()]<ve[i]) q2.pop_back();
q2.push_back(i);
while(!q2.empty()&&i-q2.front()>k-1) q2.pop_front();
if(i>=k) cout<<ve[q2.front()]<<" ";
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
小C的记事本
1.难点主要是要用栈来实现撤销操作,都在栈里,存插入或删除前的字符串为什么样的,当输入为4的时候就pop掉,然后令s=top即可
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
int dx[]={0,1,-1,0};
int dy[]={-1,0,0,1};
deque<int>q1,q2;
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int n;
while(cin>>n)
{
string s;
stack<string>st;//用来实现撤销操作的功能
//当1时插入ab,遇到4,pop掉,s=“”;
//当2时删除b,栈内放b ab,b被pop掉,s=ab便实现了撤销功能
st.push("");//记得先插入一个啥也没有的字符串
for(int i=0;i<n;i++)
{
string sb;
int x; cin>>x;
if(x==1)
{
cin>>sb;
s+=sb;
st.push(s);
}else if(x==2)
{
int pos; cin>>pos;
int l=s.size();
s.erase(l-pos,l);//删除最后k个字符
st.push(s);
}else if(x==3)
{
int pos; cin>>pos;
cout<<s[pos-1]<<endl;
}else{
st.pop();
s=st.top();
}
}
}
return 0;
}
Keep In Line
1.见注释
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
//举例子样例就是.放 q h 标q h 为1
//遇到out h 标h为0 由于q为1 所以while不执行
//in z 标z为1 队列里就是q h z
//out q 标q为0 q为队头 ans++,然后q h被pop掉,
//out z 标z为0 队列里z pop掉
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t;cin>>t;
//让插队的人跟随没有插队的人一起从队列出来,这样才可以保证队列的头一直是有效的
//每输入一次in,就把数放到队列里,然后对这个数进行标记,这个标记的作用是,使插队的人跟随没插队的pop出来
//当我们输入out但这个数并不是队头时,我们对这个数标记为0
//而当我们输入的恰好是队头时,那么首先答案+1,其次对标记过的队头重新标记为0
//因为我们要实现pop操作了,那么这一次pop便会把队头和刚刚要插队的人全pop出来
//这样相当于完成了一小部分所有人的入队出队操作,保证了下一部分也可以这样操作
while(t--)
{
int n; cin>>n;
int ans=0;
queue<string>q;
map<string,int>mp;
while(n--)
{
string op; cin>>op;
string id; cin>>id;
mp[id]++;//标记
if(op=="in") q.push(id);
else{
mp[id]=0;
if(id==q.front()){
ans++;
}
while(!q.empty()&&mp[q.front()]==0) q.pop();
//其实只有out遇到队头的数才会进行
//然后把插队的也删了
}
}
cout<<ans<<endl;
}
return 0;
}
简单的数据结构
1.按题意模拟即可
2.deque也可以实现reverse操作,也可以用下标访问每一个元素,也可以sort
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int n,m; cin>>n>>m;
deque<int>de;
while(m--){
int x;cin>>x;
if(x==1)
{
int x;cin>>x;
de.push_front(x);
}
if(x==2){
de.pop_front();
}
if(x==3){
int x;cin>>x;
de.push_back(x);
}
if(x==4){
de.pop_back();
}
if(x==5){
reverse(all(de));
}
if(x==6){
cout<<de.size()<<endl;
for(int i=0;i<de.size();i++) cout<<de[i]<<" ";
cout<<endl;
}
if(x==7)
{
sort(all(de));
}
}
return 0;
}
队列Q
1.这一题要模拟一下移动到队尾和队头的操作,具体就是使用点对存数字和下标,然后遇到first就让这个点对的y变为l,l--,遇到last,让这个点对点y变为r,r++,最后在按下标排序一下输出即可
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
bool cmp(pii a,pii b)
{
return a.y<b.y;
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int n;cin>>n;
map<int,int>mp;
//模拟移到队头、队尾操作
pii p[100005];
for(int i=0;i<n;i++){
cin>>p[i].x;
p[i].y=i;
mp[p[i].x]=i;
}
int m; cin>>m;
int l=0,r=n+1;
while(m--)
{
string s; int x;
cin>>s>>x;
int pos=mp[x];
if(s=="FIRST"){
p[pos].y=--l;
}else{
p[pos].y=++r;
}
}
sort(p,p+n,cmp);
for(int i=0;i<n;i++) cout<<p[i].x<<" ";
return 0;
}
区区区间间间
1.对原式子进行拆分一下得
max(l,r)表示区间l-r的最大值,min(l,r)表示l-r的最小值
那么问题就转化为:求所有区间长度大于1的子区间的最大值之和-最小值之和
暴力枚举肯定是过不去的
2.我们采取的策略是\(\color{red}单调栈+维护l,r数组\)
3.l数组存放的是:区间的每一个数左边第一个大于该数的下标。
r数组存放的是:区间每一个数右边第一个大于该数的下标
这个策略的思想是什么?
正面:一个区间内的最大最小值必定在这个给定的序列中
反面:求出序列中每个数所能包含的最大或最小值范围,某个区间只要在该范围内,并且取到该点,其最大最小值就是该点。
以该点为最大值或最小值的区间有两种情况:
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> pii;
#define x first
#define y second
#define all(v) v.begin(),v.end()
char c[55][55];
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
int a[100010];
int l[100010],r[100010];
int n;
int solve()
{
//x为值,y为下标
stack<pii>st;//这个单调栈的作用是用来找每个数字左边或右边【第一个】大于或小于这个数的下标
//for(auto t:a) cout<<t<<" ";
int l[n+1],r[n+1];//l数组用来存遍历到的数的左边第一个大于这个数的下标(当我们找区间最大值的和时)
//r同理
for(int i=1;i<=n;i++)
{
while(!st.empty()&&st.top().x<=a[i]) st.pop();
//如果遍历到的这个数大于栈顶这个数,说明这个数之前没有比它大的数,
//如果这个数不大于栈顶这个数,说明这个数左边第一个比它大的数就是栈里这个数
if(st.size()) l[i]=st.top().y;//因为这个数不大于,所以栈不pop元素,所以左边第一个大的数就是栈顶这个数
else l[i]=0;//没有比他大的数,所以我们把l【i】其下标设置为0
st.push({a[i],i});
}
while(!st.empty()) st.pop();
//找r数组,相当于把上面的翻转一下,只是找不到时设为n+1
for(int i=n;i>=1;i--)
{
while(!st.empty()&&st.top().x<a[i]) st.pop();//这里用<而不取等是因为,假如有两个连续重复的数,会造成
//区间数量的变化
if(st.size()) r[i]=st.top().y;
else r[i]=n+1;
st.push({a[i],i});
}
/* for(auto t:l) cout<<t<<" ";
cout<<endl;
for(auto t:r) cout<<t<<" ";*/
int ans=0;
//分两种情况讨论每个数的贡献
//1.这个数不做端点,就是不做区间的起点或结尾 ans +=(i-l【i】-1)*(r[i]-i-1);
//2.区间做端点ans+=r[i]-l[i]-2;
for(int i=1;i<=n;i++)
{
ans+=(i-l[i]-1)*(r[i]-i-1)*a[i];
ans+=(r[i]-l[i]-2)*a[i];
}
return ans;
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int t=1;
cin>>t;
while(t--) {
cin>>n;
int res=0;
for(int i=1;i<=n;i++) cin>>a[i];
res=solve();
for(int i=1;i<=n;i++) a[i]=-a[i];
res+=solve();
cout<<res<<endl;
}
return 0;
}
posted on 2024-07-16 20:18 swj2529411658 阅读(20) 评论(0) 收藏 举报
浙公网安备 33010602011771号