隐藏
「BZOJ2086」「Poi2010」Blocks - 单调栈 | Bill Yang's Blog

路终会有尽头,但视野总能看到更远的地方。

0%

「BZOJ2086」「Poi2010」Blocks - 单调栈

题目大意

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


题目分析

这是一道很神奇的题。
因为相邻的数$+1,-1$,不难想到对和的变化很小,故将所有数减去$k$做一个前缀和,转化为求和$\ge 0$的最长区间。
为什么这样是对的?因为和$\ge 0$的区间一定能够通过调整使得满足条件。

然后想办法处理和$\ge 0$的最长区间,不难想到可以通过二分在$O(n\log n)$的时间范围内求出解,但这道题显然要卡$\log$,因此必须在$O(n)$的时间内出解。

然后我就不知道怎么做了。

KEKE_046表示可以从$1\rightarrow n$维护一个$sum$单调递减的栈,再从$n\rightarrow 1$维护一个$sum$单调递增的栈 ,答案肯定在两个栈中出现,然后双指针维护一下即可。
然而在两个栈中有重复元素的情况下很难处理。

看了一下标解,标解只维护了一个从$1\rightarrow n$的单调栈,然后从$n\rightarrow1$更新答案。


如图,我们在满足题目条件的时候尽量的让栈顶变低(这样可以使得区间变得更长)。
当$sum[i-1]\ge sum[i]$时,继续让栈顶变低。
当$sum[i-1]\lt sum[i]$时,看似需要将栈顶变高,其实不需要,因为即使将栈顶变高,在$sum[i+\cdots]$时已经统计到它,且答案一定比它大,因此将栈顶变高是没有贡献的,因此维持栈顶不动即可。


代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<stack>
using namespace std;
typedef long long LL;
inline const LL Get_Int() {
LL num=0,bj=1;
char x=getchar();
while(x<'0'||x>'9') {
if(x=='-')bj=-1;
x=getchar();
}
while(x>='0'&&x<='9') {
num=num*10+x-'0';
x=getchar();
}
return num*bj;
}
const int maxn=1000005;
LL n,m,k,a[maxn],sum[maxn];
int main() {
n=Get_Int();
m=Get_Int();
for(int i=1; i<=n; i++)a[i]=Get_Int();
for(int i=1; i<=m; i++) {
k=Get_Int();
stack<int>S;
S.push(0);
for(int j=1; j<=n; j++) {
sum[j]=sum[j-1]+a[j]-k;
if(sum[S.top()]>sum[j])S.push(j);
}
int last=0,ans=0;
for(int j=n; j>=1; j--) {
while(!S.empty()&&sum[S.top()]<=sum[j]) {
last=S.top();
S.pop();
}
ans=max(ans,j-last);
}
printf("%d ",ans);
}
return 0;
}
姥爷们赏瓶冰阔落吧~