序列

内存限制:128 MiB

时间限制:2000 ms

【问题描述】

给定 n 个正整数的序列 a1,a2,a3…an,对该序列可执行下面的操作: 选择一个大于 k 的正整数 ai,将 ai 的值减去 1;选择 ai-1 或 ai+1 中的一个 值加上 1。 共给出 m 个正整数 k,对于每次给定的正整数 k,经过以上操作一定次数后, 求出最长的一个连续子序列,使得这个子序列的每个数都不小于给定的 k。

【输入格式】 第一行两个正整数 n 和 m。 第二行 n 个正整数,第 i 个正整数表示 ai ; 第三行 m 个正整数,第 i 个正整数表示第 i 次给定的 k。

【输出格式】 共一行,输出 m 个正整数,第 i 个数表示对于第 i 次给定的 k,经过一定次 数操作后,最长连续子序列的长度。

【样例输入】

5 6

1 2 1 1 5

1 2 3 4 5 6

【样例输出】

5 5 2 1 1 0

【数据规模】

对于 30%的数据,n<30

对于 50%的数据,n<=2000

对于 100%的数据,n<=1,000,000 ,m<= 50, k <= 10^9, ai <= 10^9

 

 

 

 

正解

考试时想了一个假做法,于是挂了,就不讲我的做法了。。。

很明显,把每个数都减去k之后,我们的任务就是求一个最长的序列使它的和>=0

我们可以对这个新数组进行前缀和

就变成了选两个数a,b,满足sum[a]<sum[b]&&a<b,且b-a+1最大

然后就可以O(m*n^2)了

我们可以枚举右端点,看左端点在哪里

接着我们可以维护一个神奇的不弹出的单调栈,(里面装左端点)

只要有元素比栈顶小,就把它放进栈里面,

如果比栈顶大,就跳过。(这样做既可以满足递减,又可以保证左端点的尽量靠前)

对于每一个作为右端点的数,一定可以在这个栈里找一个元素<=它(因为最小值一定在栈里)

我们可以在这个单调栈里二分查找最靠前的小于等于它的数,统计一下答案

然后就O(m*n*logn)了,还是很悬

冷静分析一波,分析我们可以倒着枚举右端点,直接弹出当前不优的栈顶(因为在右端点左移的情况下,当前不优的左端点一定不会影响未来的答案),所以我们就可以正扫一遍建单调栈,倒着做的时候边弹栈边更新答案就可以啦。

画个图:

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 1000005
#define LL long long
int a[N],stk[N],top,cnt;
long long sum[N];
int main()
{
	int n,m,i,j,k,l;
	n=gi();m=gi();
	for(i=1;i<=n;i++)
		a[i]=gi();
	while(m--){
		k=gi();top=1;stk[top]=0;
		for(i=1;i<=n;i++){
			sum[i]=sum[i-1]+1ll*a[i]-1ll*k;
			if(sum[i]<sum[stk[top]]) stk[++top]=i;
		}
		int ans=0;
		for(i=n;i>=1;i--){
			while(sum[i]>=sum[stk[top]]&&top>0) top--;
			ans=max(ans,i-stk[top+1]);
		}
		printf("%d ",ans);
	}
}