序列
内存限制: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);
}
}