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