BZOJ 2086--[Poi2010]Blocks【单调栈】

Description

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

题解

首先,题目的什么操作都是假的,将一个数加一,一个数减一,不变的是什么,是平均数啊,最后肯定能够达到一种平均状态,使子序列中的任意两个数最多相差一,所以,题目其实是让你选出最长的平均数大于等于k的子序列。

这样,我们可以先把所有数都减去k,题目又转化为去最长的加和大于等于0的子序列,立马就想到了单调栈。下面有两种解法:

法一

维护一个前缀和单调降的序列,因为对于 i<j ,且 sum[i]sum[j] ,i一定比j优秀,每次在末尾添加一个数时,若这个数比末尾的小,那么就直接入栈,否则,二分枚举栈中的最靠前的小于等于它的数,这就是以新加的数为末尾的序列的最优解了。
时间复杂度: O(mnlogn)

法二

还是维护单调降的序列,但是,并不需要二分枚举了。如果我们从后往前枚举右端点,那么,只要不停的弹栈就可以刷出答案,而不需要二分枚举了(如果以当前的栈顶为左端点也不满足的话,那么当前枚举的右端点的右边一定已经枚举到了比它更优秀的)。
时间复杂度: O(nm)

代码

下面给出法二的代码(注意此题输出末尾不能有多余空格)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 1000006
#define LL long long
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
int n,m,top,a[maxn],stack[maxn];
LL sum[maxn];
void push(int x){if(sum[x]<sum[stack[top]])stack[++top]=x;}
int main(){
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
    n=_read();m=_read();
    for(int i=1;i<=n;i++)a[i]=_read();
    while(m--){
        int x=_read();top=0;int ans=0;
        for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]-x,push(i);
        for(int i=n;i;i--){
            while(top&&sum[stack[top-1]]<=sum[i])top--;
            if(sum[stack[top]]<=sum[i])ans=max(ans,i-stack[top]);
        }
        if(m) printf("%d ",ans);
         else printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值