单调队列&单调栈专题训练(不断更新中~)

练这个专题的原因是被多校赛第三场 Problem A. Ascending Rating 虐了, 看别人博客发现只有一句解释:单调队列的应用 。

这才决定要把单调队列单调栈摸熟(感谢多校赛虐我千百遍)

1、单调队列

给出下标为1 -- n 的数组 ,从左到右扫(也可以从右向左), 对于当前元素i , 从队尾不断地把小于a[i]的出队(规则自己定)

维护一个从队头到队尾递减的队列。多用于维护区间内最大值, 或者区间内递增序列包含的元素个数。

2)Problem A. Ascending Rating

题意:

给1-n数组,每次移动长度m的窗口,求窗口第一个元素 到 窗口内最大元素之间递增序列个数, 和最大元素的值(具体题意不是这样的 ,只不过求得上诉数据再处理一下就得到答案了)

思路:

本题考虑的是对于 i 而言,[i,i+m-1]区间对 i位置 的影响, 因此此题从右往左扫。满足区间长度为m 后才开始计算值

维护一个长度为m的严格递减单调队列, 队列里的内容就是当前元素a[i] 向后扫的 严格大于a[i]的元素。且队首元素就是区间m内的最大值

代码:


#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
#include <queue>
#include <cstring>

using namespace std;
const int maxn = 1e7+5;
typedef long long ll;
ll a[maxn], qq[maxn];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ll n,m,k,p,q,r,mod;
        scanf("%lld%lld%lld%lld%lld%lld%lld",&n,&m,&k,&p,&q,&r,&mod);
        ll i=1;
        for(; i<=k; i++)
            scanf("%lld",&a[i]);
        while(i<=n)
        {
            a[i] = (p*a[i-1]+q*i+r)%mod;
            i++;
        }

        ll tail = 0,head = 1; // head 队头 tail队尾,因为是 ++tail  = i 所以tail置0
        ll ansa = 0,ansb = 0;

        for(ll i=n;i;i--)
        {
            while(tail>=head && a[qq[tail]] <= a[i]) // 当前元素 ,把队尾的小弱鸡全踢出去
                tail--;
            qq[++tail] = i;
            if(i<=n-m+1) // 后面区间长度 m
            {
                while(tail>=head && qq[head]>i+m-1) // 超出区间m了
                    head++;

                ansa += i^a[qq[head]];
                ansb += i^(tail-head+1);

            }
        }
        printf("%lld %lld\n",ansa,ansb);
    }
    return 0;
}

2)淹没木板

转自https://www.cnblogs.com/tham/p/8038828.html

问题描述
地上从左到右竖立着 n 块木板,从 1 到 n 依次编号,如下图所示。我们知道每块木板的高度,在第 n 块木板右侧竖立着一块高度无限大的木板,现对每块木板依次做如下的操作:对于第 i 块木板,我们从其右侧开始倒水,直到水的高度等于第 i 块木板的高度,倒入的水会淹没 ai 块木板(如果木板左右两侧水的高度大于等于木板高度即视为木板被淹没),求 n 次操作后,所有 ai 的和是多少。如图上所示,在第 4 块木板右侧倒水,可以淹没第 5 块和第 6 块一共 2 块木板,a4 = 2。

思路:

这是个简单题,有多种思路, 从左向右扫, 把队尾元素小于当前元素的弹出队,1)则剩余在队列内的,就是当前能对其的贡献,ans += tail - head +1 。 2)弹出队的,当前点对他产生价值,ans += i - qq[tail]-1

代码:第一种


#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
#include <queue>
#include <cstring>

using namespace std;
const int maxn = 1e7+5;
typedef long long ll;
int a[maxn], qq[maxn];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        int head = 1, tail = 0;

        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);

        int ans = 0;
        for(int i=1; i<=n; i++)
        {
            while(head <= tail && a[qq[tail]] < a[i])
                tail --;
            ans += tail - head + 1;
            qq[++tail] = i;
        }
        printf("%d\n",ans);

    }
    return 0;
}

3)单调队列优化DP

经过几个月辛勤的工作,FJ决定让奶牛放假。假期可以在1…N天内任意选择一段(需要连续),每一天都有一个享受指数W。但是奶牛的要求非常苛刻,假期不能短于P天,否则奶牛不能得到足够的休息;假期也不能超过Q天,否则奶牛会玩的腻烦。FJ想知道奶牛们能获得的最大享受指数。

Input

第一行:N,P,Q.
第二行:N个数字,中间用一个空格隔开,每个数都在longint范围内。

Output

一个整数,奶牛们能获得的最大享受指数。

Sample Input

5 2 4
-9 -4 -3 8 -6

Sample Output

5

Data Constraint

50% 1≤N≤10000
100% 1≤N≤100000
1<=p<=q<=n

思路:

求区间和,那么就用前缀和思想 ,令sum【r】 = sun[r-1]+a[r] ,那么区间[l , r]的和就等于 sum[r] - sum[l - 1] 。我们令R固定,L的取值范围是 R-Q+1 <= L <= R-P+1 ,则状态转移方程为 DP[R] = DP [J] + a[R] ( R-Q <= L <= R-P max(DP[J]) )使用单调队列维护区间范围 [ R-Q , R-P] 内的sum[J]数组最小值。

代码:

#include <iostream>
#include <bits/stdc++.h>

using namespace std;
const int maxn = 10050;
const int inf = 0x3f3f3f3f;
int dp[maxn], a[maxn],  qq[maxn];

int main()
{
    int n, p ,q ;
    scanf("%d%d%d",&n,&p,&q);
    a[0] = 0;
    for(int i=1; i<=n; i++)
    {   int t;
        scanf("%d",&t);
        a[i] = a[i-1] + t; // 前缀和
    }

    int head = 1, tail = 0;
    int ans = -inf;
    for(int i=p; i<=n; i++)
    {
        while(head <= tail && a[i-p] <= a[qq[tail]])
            tail--;

        while(head <= tail && qq[head]<i-q)
            head++;

        qq[++tail] = i-p;
        ans = max(ans, a[i] - a[qq[head]]);

    }

    printf("%d\n",ans);
    return 0;
}

2、单调栈

多用于找到当前点左边(右边) 第一个大于当前值(小于)的位置。

例如希望找到左边第一个比当前大的元素, 加入一个元素之前,对栈顶进行操作,遇到比他小的 ,出栈, 否则就是我们要的结果

还是淹没木板那题

发现做法和单调队列很像, 数组模拟仅仅是踢出元素方式不同, 不过在其他应用里 这两种数据结构还是有不一样的奇效

代码:

include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
#include <queue>
#include <cstring>

using namespace std;
const int maxn = 1e7+5;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int a[maxn], qq[maxn];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);


        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);

        int ans = 0;
        int top  = 0, bottom = 1;
        a[n+1] = inf; // n+1 个板子,无穷大

        for(int i=1; i<=n+1; i++)
        {
            while(top >=  bottom && a[qq[top]] < a[i])
            {
                ans += i - qq[top] - 1; // 比如第三个板子 8  此时栈顶是 第二个板子5  则贡献是 3-2-1 = 0
                top--;

            }
            qq[++top] = i;
        }

        printf("%d\n",ans);

    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37591656/article/details/81304220