堆栈队列单调栈等

知识点

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.对原式子进行拆分一下得

\[\sum^n_{l=1}\sum^n_{r=l+1}v_{l,r}=\sum^n_l\sum^n_{r=l+1}(max(l,r))-\sum^n_l\sum^n_{r=l+1}(min(l,r)) \]

max(l,r)表示区间l-r的最大值,min(l,r)表示l-r的最小值
那么问题就转化为:求所有区间长度大于1的子区间的最大值之和-最小值之和

暴力枚举肯定是过不去的

2.我们采取的策略是\(\color{red}单调栈+维护l,r数组\)

3.l数组存放的是:区间的每一个数左边第一个大于该数的下标。
r数组存放的是:区间每一个数右边第一个大于该数的下标

这个策略的思想是什么?
正面:一个区间内的最大最小值必定在这个给定的序列中
反面:求出序列中每个数所能包含的最大或最小值范围,某个区间只要在该范围内,并且取到该点,其最大最小值就是该点。
以该点为最大值或最小值的区间有两种情况:

\[1.该点做区间的起点或终点 \]

\[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()
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)    收藏  举报

导航