BZOJ2086: [Poi2010]Blocks

题目大意:给出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 1000010
using 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();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值