[POI2010]KLO-Blocks(单调栈)
题意
给出N个正整数a[1..N],再给出一个正整数k,现在可以进行如下操作:每次选择一个大于k的正整数a[i],将a[i]减去1,选择a[i-1]或a[i+1]中的一个加上1。经过一定次数的操作后,问最大能够选出多长的一个连续子序列,使得这个子序列的每个数都不小于k。M组询问
n<=1000000,m<=50;
题解
这题一开始想的是二分,但瞟了一眼范围觉得会T但是,觉得可以优化,所以就先写二分了。
结果写挂了(不是T是WA了)然后优化也没来得及想就放弃了。(最后我也不知道怎么挂的,能不能用二分)
我们把所有数减去k再求前缀和。
所以就是求sum[i]-sum[j-1]>0且i-j最大
假设我们已经确定了右端点,我们考虑两个左端点i,j。假设i<j且sum[i]<sum[j],那么j显然是没有用的。
所以我们可以求出一个下标递增,sum递减的序列,用单调栈解决。
然后我们从n到1枚举右端点。
这个端点i对应的左端点j满足sum[i]>=sum[j]且j最小。
然后我们一个一个弹栈,直到sum[j]>sum[i]此时最后一个被弹出栈的下标就是这个端点对应的答案。
具体实现看代码
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 const long long N=1000100; 8 long long n,a[N],m,k,b[N],sum[N],ans,top,stack[N]; 9 void work(){ 10 top=1; 11 stack[1]=0; 12 for(long long i=1;i<=n;i++){ 13 if(sum[i]<sum[stack[top]])stack[++top]=i; 14 } 15 stack[top+1]=n; 16 for(long long i=n;i>=1;i--){ 17 while(top&&sum[stack[top]]<=sum[i]){ 18 top--; 19 } 20 ans=max(ans,i-stack[top+1]); 21 } 22 } 23 int main(){ 24 scanf("%lld%lld",&n,&m); 25 for(long long i=1;i<=n;i++){ 26 scanf("%lld",&a[i]); 27 } 28 for(long long i=1;i<=m;i++){ 29 // cout<<i<<"ashdfkjsdfhsd"<<endl; 30 scanf("%lld",&k); 31 ans=0; 32 for(long long i=1;i<=n;i++){ 33 b[i]=a[i]-k; 34 sum[i]=sum[i-1]+b[i]; 35 } 36 work(); 37 printf("%lld ",ans); 38 } 39 return 0; 40 }