BZOJ 2086 [Poi2010]Blocks 单调栈

题意:
给出N个正整数a[1..N],再给出一个正整数k,现在可以进行如下操作:每次选择一个大于k的正整数a[i],将a[i]减去1,选择a[i-1]或a[i+1]中的一个加上1。经过一定次数的操作后,问最大能够选出多长的一个连续子序列,使得这个子序列的每个数都不小于k。
总共给出M次询问,每次询问给出的k不同,你需要分别回答。

第一行两个正整数N (N <= 1,000,000)和M (M<=50)
第二行N个正整数,第i个正整数表示a[i] (a[i]<=109)
第三行M个正整数,第i个正整数表示第i次询问的k (k<=109)
解析:
解决办法倒是很好想,不过怎么实现?
办法其实就是把每一个数与k作差然后求一个前缀和,之后我们要求max(j-i)并且sum[j]-sum[i]>=0
初步思路就是搞一个单调数据结构来解这个问题。
后来单调栈写的我都蛋疼了,一定是做法出了问题。
我们首先明确,如果我们倒着找每一个数最左边的点在哪的话,那么对于答案来说,左边的点显然是单调的。
所以我们可以考虑首先搞出来一个sum的单调递减序列。
之后倒着枚举整个sum,从这个单调递减序列中找到对于答案有贡献的点最左边能到哪即可。不断弹栈即可完成这一过程。
代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1000100
using namespace std;
typedef long long ll;
int n,m;
ll a[N];
ll sum[N];
int top;
int sta[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=m;i++)
    {
        ll qx;
        scanf("%lld",&qx);
        for(int j=1;j<=n;j++)sum[j]=sum[j-1]+a[j]-qx;
        top=0;
        sta[++top]=0;
        for(int j=1;j<=n;j++)
            if(sum[j]<sum[sta[top]])sta[++top]=j;
        int ans=0;
        for(int j=n;j>=0;j--)
        {
            while(top>0&&sum[j]>=sum[sta[top]])top--;
            ans=max(ans,j-sta[top+1]); 
        }
        if(i==m)printf("%d\n",ans);
        else    printf("%d ",ans);
    }
} 

你可能感兴趣的:(poi,栈,2010)