网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 其他技术区 > 算法与数据结构 >

学习笔记——单调数据结构

时间:2026-02-15 16:32

人气:

作者:admin

标签:

导读:单调栈 题目 定义:一个下标 $i$ 是序列 $a$ 的后缀最大值下标当且仅当对于所有的 $i lt; j \le |a| $ ,都有 $a_igt;a_j$。其中 $|a|$ 表示序列 $a$ 的长度。给出整数 $n$ 和一个长为 $n$ 的序列...

题目

定义:一个下标 \(i\) 是序列 \(a\) 的后缀最大值下标当且仅当对于所有的 $i < j \le |a| $ ,都有 \(a_i>a_j\)。其中 \(|a|\) 表示序列 \(a\) 的长度。给出整数 \(n\) 和一个长为 \(n\) 的序列 \(a\),对于每个 \(1 \le i \le n\) ,输出 \(a_1 \sim a_i\) 所有后缀最大值下标的异或和。

思路

考虑用一个栈来维护当前 \(a_1 \sim a_i\) 所有的后缀最大值下标,容易发现栈中的数单调递增(指栈顶到栈底的下标对应的值单调递增)。当遍历到 \(i\) 时,如果栈顶存放的下标 \(j\) 满足 \(a_j>a_i\),则因为栈是单调递增的,栈中的其他下标对应的值均大于 \(a_i\),可以正确维护。如果不满足条件则需要不断弹出栈顶直到满足条件。并将 \(i\) 入栈,栈中的下标即为后缀最大值下标

Code:

vector<int> stk;
int ans=0;
for(int i=1;i<=n;i++){
    while(!stk.empty()&&a[stk.back()]<=a[i]){
        ans^=stk.back();
        stk.pop_back();
    }
    stk.push_back(i);
    cout<<ans<<" ";
}

复杂度

共有 \(n\) 次循环且共执行了 \(n\) 次入栈操作,时间复杂度 \(O(n)\)

单调栈需要额外空间,空间复杂度 \(O(n)\)

题目

将单调栈的问题稍加变化:给出整数 \(n, k\) 和一个长为 \(n\) 的序列 \(a\),对于每个长度为 \(k\) 的区间,输出该区间内所有后缀最大值下标的数量。

思路

这个问题对比上个问题来说多了一个删除过期元素的功能。可以用 STL 中的 deque 双端队列来维护。每次循环开始时先从队首(front)取出过期元素。再按照单调栈的方法维护

单调队列还可以求区间最小/最大值,将新的下标加入队列中后队首的下标就是最值所在的下标

Code

deque<int> q;
int ans=0;
for(int i=1;i<=n;i++){
    while(!q.empty()&&q.front()<=i-k){
        ans--;
        q.pop_front();
    }
    while(!q.empty()&&a[q.back()]<=a[i]){
        ans--;
        q.pop_back();
    }
    q.push_back(i);
    ans++;
    if(i>=k)cout<<ans<<" ";
}

复杂度

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

P6510 奶牛排队

题意

给定一个长度为 \(n\) 的序列 \(a\),求出满足区间内 \(a_l\) 最小且 \(a_r\) 最大的区间 \([l,r]\) 的最大长度。( \(2 \le N \le 10^5\)

思路

考虑暴力,枚举右端点 \(r\) ,对每个 \(r\) 求出符合条件的 \(l\) 。时间复杂度 \(O(n^2)\) ,对 \(10^5\) 的数据显然会超时。

因为 \(a_l < a_i\)\(l < i \le r\) ),所以 \(l\) 一定是 \(1 \sim r\) 的后缀最小值。又因为 \(l \sim r\)\(a_r\) 最大,即这段区间不能有任何一个数比 \(a_r\) 大。可以二分求出 \(l\) 的值。

用单调栈 \(s1\)\(s2\) 分别维护后缀最大值和最小值,每次循环时在 \(s2\) 上二分找出第一个大于 \(s1.back()\) (即栈顶)的下标,更新答案。时间复杂度 \(O(n \log n)\)

Code

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n; cin>>n;
    vector<int> a(n+1);
    for(int i=1;i<=n;i++)cin>>a[i];
    vector<int> s1,s2;
    int ans=0;
    for(int r=1;r<=n;r++){
        while(s1.size()&&a[s1.back()]<=a[r])
            s1.pop_back();
        while(s2.size()&&a[s2.back()]>=a[r])
            s2.pop_back();
        auto l=upper_bound(s2.begin(),s2.end(),s1.back());
        if(l!=s2.end())ans=max(ans,r-*l+1);
        s1.push_back(r);
        s2.push_back(r);
    }
    cout<<ans;
    return 0;
}

P13889 子矩阵

题意

有一个 \(n \times m\) 个整数组成的矩阵,求所有大小为 \(a \times b\) 的子矩阵中最大值与最小值的乘积的和。

思路

考虑暴力做法,先找出每个子矩形,再计算最小值和最大值,统计答案,时间复杂度 \(O(nmab)\) ,显然不能通过本题。

我们可以使用单调队列进一步优化,对于输入的 \(n \times m\) 的矩阵,我们可以把每一行看作一个单独的数列,对其求出它的每个长度为 \(b\) 的子段的区间最大值,易得出这样的最大值有 \(n \times (m-b+1)\) 个,将每行的最大值构成新矩阵 \(mx1\) 的一行,则 \(mx1\) 的大小是 \(n \times (m-b+1)\)

对新矩阵,再将它的每一列看作一个单独的数列,求出这一列上每个长度为 \(a\) 的子段的区间最大值,存到另一个矩阵 \(mx2\) 中, \(mx2\) 的大小就是 \((n-a+1) \times (m-b+1)\) 。最小值同理。

经过这一步,我们可以得出两个存储原矩阵的子矩阵最值的矩阵,最后统计答案即可。

Code

#include<bits/stdc++.h>
using namespace std;


int main(){
    int n,m,a,b; cin>>n>>m>>a>>b;
    vector<vector<int>> c(n+1,vector<int>(m+1));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>c[i][j];
        }
    }


    vector<vector<int>> mx1(n+1,vector<int>(m-b+2)),
    mn1(n+1,vector<int>(m-b+2));
    vector<vector<int>> mx2(n-a+2,vector<int>(m-b+2)),
    mn2(n-a+2,vector<int>(m-b+2));


    for(int i=1;i<=n;i++){
        deque<int> q;
        for(int j=1;j<=m;j++){
            while(q.size()&&q.front()<j-b+1)q.pop_front();
            while(q.size()&&c[i][q.back()]<=c[i][j])q.pop_back();
            q.push_back(j);
            if(j>=b)mx1[i][j-b+1]=c[i][q.front()];
        }
    }
    for(int j=1;j<=m-b+1;j++){
        deque<int> q;
        for(int i=1;i<=n;i++){
            while(q.size()&&q.front()<i-a+1)q.pop_front();
            while(q.size()&&mx1[q.back()][j]<=mx1[i][j])q.pop_back();
            q.push_back(i);
            if(i>=a)mx2[i-a+1][j]=mx1[q.front()][j];
        }
    }

    for(int i=1;i<=n;i++){
        deque<int> q;
        for(int j=1;j<=m;j++){
            while(q.size()&&q.front()<j-b+1)q.pop_front();
            while(q.size()&&c[i][q.back()]>=c[i][j])q.pop_back();
            q.push_back(j);
            if(j>=b)mn1[i][j-b+1]=c[i][q.front()];
        }
    }
    for(int j=1;j<=m-b+1;j++){
        deque<int> q;
        for(int i=1;i<=n;i++){
            while(q.size()&&q.front()<i-a+1)q.pop_front();
            while(q.size()&&mn1[q.back()][j]>=mn1[i][j])q.pop_back();
            q.push_back(i);
            if(i>=a)mn2[i-a+1][j]=mn1[q.front()][j];
        }
    }

    long long ans=0;
    long long mod=998244353;
    for(int i=1;i<=n-a+1;i++){
        for(int j=1;j<=m-b+1;j++){
            ans=(ans+(mx2[i][j]*1LL*mn2[i][j]%mod))%mod;
        }
    }
    cout<<ans;
    return 0;
}

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信