单调队列 和 单调栈

//顾名思义, 就是维护一个严格单调递增或递减的序列. 队列和栈的区别就是他们所特有的性质, 用数组模拟.

poj 2823 单调队列
//这个就是一个最简单的单调队列的应用, 具体解释看代码. 后面有进阶的单调队列 .

const int maxn=1e6+5;
int maxq[maxn],minq[maxn];
int maxans[maxn],minans[maxn],num[maxn];
int head1,tail1;
int head2,tail2;
void solve()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=0;i0;
            if(head1q[head1] <= i - k) head1++;
            if(head2q[head2] <= i - k) head2++;

            scanf("%d",&num[i]);
            while( head1q[tail1-1]] <= num[i]) tail1--;
            tail1++;
            maxq[tail1-1] = i;
            while( head2q[tail2-1]] >= num[i]) tail2--;
            tail2++;
            minq[tail2-1]=i;
            maxans[i]=num[maxq[head1]];
            minans[i]=num[minq[head2]];
        }
        for(int i=k-1;iprintf("%d%c",minans[i],i==n-1?'\n':' ');
        for(int i=k-1;iprintf("%d%c",maxans[i],i==n-1?'\n':' ');
    }
}

AC Code 记得用C++, G++会T.

const int maxn=1e6+5;
int maxq[maxn],minq[maxn];   //单调队列的都是存的那个对应的数的下标.
int maxans[maxn],minans[maxn],num[maxn];
int head1,tail1;
int head2,tail2;
void solve()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=0;i0;
            //始终保持一个长度为k的队列,这里面的最大值和最小值都是在长度为k的序列中可用的.
            //除非判一下是否当前窗口已经越过了它,就是判断一下下标.
            //删除下标超出范围的对首元素.
            if(head1q[head1] <= i - k) head1++;
            if(head2q[head2] <= i - k) head2++;

            //删除队尾元素
            scanf("%d",&num[i]);
            while( head1q[tail1-1]] <= num[i]) tail1--;
            tail1++;     //就是不断让对首位最大,如果后面进队的比当前的队尾大,就删去队尾.否则就添加进去.
            maxq[tail1-1] = i;   //这样当对首被删去后,依然可以保证后面一个就是最大.
            while( head2q[tail2-1]] >= num[i]) tail2--;
            tail2++;
            minq[tail2-1]=i;
            maxans[i]=num[maxq[head1]];
            minans[i]=num[minq[head2]];
        }
        for(int i=k-1;iprintf("%d%c",minans[i],i==n-1?'\n':' ');
        for(int i=k-1;iprintf("%d%c",maxans[i],i==n-1?'\n':' ');
    }
}

HDU – 3530 单调队列进阶
//题意: 求一个最长子串其中 最大值 - 最小值 要介于m和k之间. (下次看到类似的题目就要想起这道题哈)
//思路: 用二个单调队列维护最大和最小值.
//推荐一道类似的题: 点这呀
// 提示: 串的长度和序列个数的关系!!!
AC Code

/** @Cain*/
const int maxn=1e5+5;
int a[maxn];
int maxq[maxn],minq[maxn];
int head1,tail1;
int head2,tail2;
void solve()
{
    int n,m,k;
    while(~scanf("%d%d%d",&n,&m,&k)){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        head1 = tail1 = head2 = tail2 = 0;
        int res = 0;
        int tmp = 0;   //保存上一次的位置.
        for(int i=1;i<=n;i++){
            while(head1 < tail1 && a[maxq[tail1-1]] < a[i] ) tail1--;
            while(head2 < tail2 && a[minq[tail2-1]] > a[i] ) tail2--;
            maxq[tail1++] = i;
            minq[tail2++] = i;

            while(a[maxq[head1]] - a[minq[head2]] > k){
                if(maxq[head1] < minq[head2] ) tmp = maxq[head1++] ;  //头指针移动,并且tmp记录改变位置.
                else tmp = minq[head2++] ;   //请一定注意好下标位置关系.
            }                                //我的tmp从零开始,所以长度就是当前的i-tmp. 不懂就把图画出来.
            if(a[maxq[head1]] - a[minq[head2]] >= m) res = max(res,i-tmp);
        }
        printf("%d\n",res);
    }
}

poj – 2559 单调栈
//题意 : 给定从左到右多个矩形,已知这此矩形的宽度都为1,长度不完全相等。这些矩形相连排成一排,求在这些矩形包括的范围内能得到的面积最大的矩形,打印出该面积。所求矩形可以横跨多个矩形,但不能超出原有矩形所确定的范围

//思路 : 建立一个单调递增栈,所有元素各进栈和出栈一次即可。每个元素出栈的时候更新最大的矩形面积.
设栈内的元素为一个二元组(x, y),x表示矩形的高度,y表示矩形的宽度.
若原始矩形高度分别为2,1,4,5,1,3,3高度为2的元素进栈,
当前栈为(2,1)
高度为1的元素准备进栈,但必须从栈顶开始删除高度大于或等于1的矩形,因为2已经不可能延续到当前矩形。删除(2,1)这个元素之后,更新最大矩形面积为2*1=2,然后把它的宽度1累加到当前高度为1的准备进栈的矩形,然后进栈,当前栈为(1,2)
高度为4的元素进栈,当前栈为(1,2) (4,1)
高度为5的元素进栈,当前栈为(1,2) (4,1) (5,1)
高度为1的元素准备进栈,删除(5,1)这个元素,更新最大矩形面积为5*1=5,把1累加到下一个元素,得到(4,2),删除(4,2),更新最大矩形面积为4*2=8,把2累加到下一个元素,得到(1,4),1*4=4<8,不必更新,删除(1,4),把4累加到当前准备进栈的元素然后进栈,当前栈为(1,5)
高度为3的元素进栈,当前栈为(1,5) (3,1)
高度为3的元素准备进栈,删除(3,1),不必更新,把1累加到当前准备进栈的元素然后进栈,当前栈为(1,5) (3,2)
把余下的元素逐个出栈,(3,2)出栈,不必更新,把2累加到下一个元素,当前栈为(1,7),(1,7)出栈,不必更新. 栈空,结束.
最后的答案就是8.

空板:

const int maxn=1e5+5;
int a[maxn];
struct node
{
    int h,w;
}s[maxn];
void solve()
{
    int n;while(~scanf("%d",&n) && n){
        for(int i=0;iscanf("%d",&a[i]);
        }
        Fill(s,0);
        int top = 0;
        ll ans = 0;
        ll tot = 0;
        for(int i=0;i0;
            while(top > 0 && s[top-1].h >= a[i]){
                tot = s[top-1].h * (s[top-1].w + tmp);
                if(tot > ans) ans =tot;
                tmp += s[top-1].w;
                top--;
            }
            s[top].h = a[i];
            s[top].w = 1+tmp;
            top++;
        }
        ll tmp = 0;
        while(top>0){
            tot = s[top-1].h * (s[top-1].w + tmp);
            if(tot > ans) ans =tot;
            tmp += s[top-1].w;
            top--;
        }
        printf("%lld\n",ans);
    }
}

解释版:

const int maxn=1e5+5;
int a[maxn];
struct node
{
    int h,w;
}s[maxn];
void solve()
{
    int n;while(~scanf("%d",&n) && n){
        for(int i=0;iscanf("%d",&a[i]);
        }
        Fill(s,0);
        int top = 0;
        ll ans = 0;
        ll tot = 0;
        for(int i=0;i0;    //记录要将宽度累加的下一个的点的值.
            while(top > 0 && s[top-1].h >= a[i]){
                tot = s[top-1].h * (s[top-1].w + tmp);  //乘之前累加的宽度.
                if(tot > ans) ans =tot;
                tmp += s[top-1].w;   //把累加的宽度记下来.
                top--;               //如果可以删,就把官渡给一个,这样才能尽量使结果最大,
            }                        //如果不删了,就把累加的结果给新入栈的元素.
            s[top].h = a[i];
            s[top].w = 1+tmp;
            top++;
        }
        ll tmp = 0;
        while(top>0){
            tot = s[top-1].h * (s[top-1].w + tmp);
            if(tot > ans) ans =tot;
            tmp += s[top-1].w;
            top--;
        }
        printf("%lld\n",ans);
    }
}

CF – 817D 思维 + 单调栈
//题意: 给你个a序列, 求a序列的任意连续子序列中最大值-最小值的值的和.
//思路: 我们考虑每一个数对答案的贡献, 在一段连续的区间中, 这个数要么作为最大值, 要么作为最小值, 这样我们就设L[i]是第 i 个数它往左最大可以到的位置, R[i]是 i 往右最大可以到的位置, 这样答案就加上
ans += a[i] ×(i - L[i])×(R[i] - i ). 同理一样找最小的位置, ans-= a[i] ×(i - L[i])×(R[i] - i ), 然后输出答案即可.
AC Code (空)

/** @Cain*/
const int maxn=1e6+5;
int a[maxn],pos[maxn];
int maxl[maxn],maxr[maxn];
int minl[maxn],minr[maxn];
void solve()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    a[0] = inf;
    int top = 1;
    pos[top] = 0;
    for(int i=1;i<=n;i++){
        while(a[pos[top]] <= a[i]) top--;
        maxl[i] = pos[top];
        pos[++top] = i;
    }
    a[n+1] = inf;
    top = 1;
    pos[top] = n+1;
    for(int i=n;i>=1;i--){
        while(a[pos[top]] < a[i]) top--;
        maxr[i] = pos[top];
        pos[++top] = i;
    }
    ll res = 0;
    for(int i=1;i<=n;i++) res += 1ll * a[i] * (i - maxl[i] ) * ( maxr[i] - i);
    a[0] = -inf;
    top = 1;
    pos[top] = 0;
    for(int i=1;i<=n;i++){
        while(a[pos[top]] >= a[i]) top--;
        minl[i] = pos[top];
        pos[++top] = i;
    }
    a[n+1] = -inf;
    top = 1;
    pos[top] = n+1;
    for(int i=n;i>=1;i--){
        while(a[pos[top]] > a[i]) top--;
        minr[i] = pos[top];
        pos[++top] = i;
    }
    for(int i=1;i<=n;i++) res -= 1ll * a[i] * (i - minl[i] ) * ( minr[i] - i );
    printf("%I64d\n",res);
}

解释版:

/** @Cain*/
const int maxn=1e6+5;
int a[maxn],pos[maxn];
int maxl[maxn],maxr[maxn];
int minl[maxn],minr[maxn];
void solve()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    a[0] = inf;
    int top = 1;
    pos[top] = 0;
    //往左找最大到的位置.
    for(int i=1;i<=n;i++){
        while(a[pos[top]] <= a[i]) top--;
        maxl[i] = pos[top];  //好好理解, 如果当前位大于了上一位, 则top可以直接跳到大于上一位的位置
        pos[++top] = i;    //在进行判断, 这样跳到更快. 也就是单调栈的应用. 维持一个最大的栈底.
    }                        //如果将要进来的元素大于此时的栈顶,就把栈顶删去. 直到为空. 然后把新加进来的元素放进去.
    a[n+1] = inf;
    top = 1;
    pos[top] = n+1;
    for(int i=n;i>=1;i--){
        while(a[pos[top]] < a[i]) top--;    //注意不要统计重了.
        maxr[i] = pos[top];
        pos[++top] = i;
    }
    ll res = 0;
    for(int i=1;i<=n;i++) res += 1ll * a[i] * (i - maxl[i] ) * ( maxr[i] - i);
    a[0] = -inf;
    top = 1;
    pos[top] = 0;
    for(int i=1;i<=n;i++){
        while(a[pos[top]] >= a[i]) top--;
        minl[i] = pos[top];
        pos[++top] = i;
    }
    a[n+1] = -inf;
    top = 1;
    pos[top] = n+1;
    for(int i=n;i>=1;i--){
        while(a[pos[top]] > a[i]) top--;      //同样注意.
        minr[i] = pos[top];
        pos[++top] = i;
    }
    for(int i=1;i<=n;i++) res -= 1ll * a[i] * (i - minl[i] ) * ( minr[i] - i );
    printf("%I64d\n",res);
}

你可能感兴趣的:((单调)队列和(单调)栈)