BZOJ2086: [Poi2010]Blocks

来源:互联网 发布:广州网络批发服装市场 编辑:程序博客网 时间:2024/06/05 12:38

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


这个询问条件可以转化为询问最长的平均数大于等于K的区间

对于每组询问我们分别计算,先给每个数全都减去K,这样就相当于问平均数大于等于0的最长区间,也就是区间和大于等于0的最长区间

我们做一个前缀和,相当于求max(i-j),pre[i]>=pre[j]

这一步怎么求呢?我们可以先从左到右扫一遍,每当当前点的值小于栈顶时,就把它压入栈顶,这样扫完之后在栈里的元素就是所有可能的左端点


然后开始枚举右端点,一直弹栈直到栈顶比他大,弹出去的最后一个元素就是以他为右端点最靠左的左端点

这样为什么一定能更新出答案呢?因为当你求出一个值之后,右端点会继续向左移动,所以左端点一定也要向左移动才可能更新出更优的解,所以之前弹出去的那些元素就已经没用了,弹出去就好


#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#define N 1000010using namespace std;long long a[N],b[N],pre[N];long long s[N],t;long long n,m;bool F;void solve(){long long i,j,x,y;t=1;s[1]=0;for(i=1;i<=n;i++)if(pre[s[t]]>pre[i]) {t++;s[t]=i;}long long ans=0;s[t+1]=n;for(i=n;i>=1;i--){while(t&&pre[s[t]]<=pre[i]) t--;ans=max(ans,i-s[t+1]);}printf("%lld",ans);if(!F) printf(" ");}int main(){scanf("%lld%lld",&n,&m);long long i,j,x,y;for(i=1;i<=n;i++)scanf("%lld",&a[i]);for(i=1;i<=m;i++){scanf("%lld",&x);for(j=1;j<=n;j++){b[j]=a[j]-x;pre[j]=pre[j-1]+b[j];}if(i==m) F=true;solve();}}

0 0
原创粉丝点击