POI2010 题解整理
Description
给出 N 个正整数 a[1…N] ,再给出一个正整数 k ,现在可以进行如下操作:每次选择一个大于 k 的正整数 ai ,将 ai -1,选择 ai−1 或 ai+1 中的一个+1。
经过一定次数的操作后,问最大能够选出多长的一个连续子序列,使得这个子序列的每个数都不小于 k 。总共给出 M 次询问,每次询问给出的 k 不同,你需要分别回答。
Input
- 第一行两个正整数 N(N≤106) 和 M(M≤50) 。
- 第二行 N 个正整数,第 i 个正整数表示 ai(ai≤109) 。
- 第三行 M 个正整数,第 i 个正整数表示第i次询问的 k(k≤109) 。
Output
- 共一行,输出M个正整数,第i个数表示第i次询问的答案。
Sample Input
5 6
1 2 1 1 5
1 2 3 4 5 6
Sample Output
5 5 2 1 1 0
Solution :
这道题目和牛宫一题非常像。
首先要得到这条式子: ans=max{R−L:∑Ri=1ai−∑Lj=1aj≥(R−L)⋅k} 。
暂且设 sumi=∑ij=1aj−i⋅k ,则对于每一个 R ,要找 [0,R−1] 内最小的 L ,并且满足 sumR≥sumL 。
有一个性质:
所以我们可以维护一个单调栈,当满足 sumstk[top]>sumi 的时候才将i放入。那么我们就会发现这个单调栈呈现这样一种情况:下标在不断增大,但是 sumpos 在不断减小。于是我们找到栈中最小的 pos ,且满足 sumpos≤sumi 即可。
#include
#define M 1000005
using namespace std;
long long sum[M];
inline void Rd(int &res){
res=0;char c;
while(c=getchar(),c<48);
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>47);
}
int stk[M],top=0;
int main(){
int n,m;Rd(n),Rd(m);
for(int i=1,x;i<=n;i++){
Rd(x);sum[i]=sum[i-1]+x;
}
for(int _=1;_<=m;++_){
int k;Rd(k);
top=0;
int ans=0;
stk[++top]=0;
for(int i=1;i<=n;i++){
if(sum[stk[top]]-1LL*stk[top]*k>sum[i]-1LL*i*k)stk[++top]=i;
else{
int L=1,R=top,res=-1;
while(L<=R){
int mid=L+R>>1;
if(sum[stk[mid]]-1LL*stk[mid]*k<=sum[i]-1LL*i*k){
R=mid-1;
res=mid;
}else L=mid+1;
}
if(~res)ans=max(ans,i-stk[res]);
}
}
printf("%d%c",ans,_==m?'\n':' ');
}
}
(16/11/17更新)为了将二分的 O(logn) 复杂度转化为线性复杂度,我们需要回到最开始的模拟中——我们枚举当前的右端点,然后将左端点的位置不断向左滑动。由于我们不能确定,在当前的 sumL>sumR 的前方,是否有比 sumL 更小的值可以转移,所以单次询问我们暴力的复杂度为 O(N2) 。
但是对于每个位置,前面最小的值可以利用动态规划在 O(n) 时间内计算。于是我们上述缺陷的判断就可以被填补,我们在滑动L的时候,只要判断前面最小的值是否 ≤sumR 即可。不能滑动时,我们不必撤销当前的最优解并且下一轮从头滑动L,我们保持这个区间向右滑动即可。这样此处均摊的复杂度仍为 O(n) 。
综上,最后总复杂度为 O(nm) 。
#include
#define M 1000005
using namespace std;
long long sum[M];
inline void Rd(int &res){
res=0;char c;
while(c=getchar(),c<48);
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>47);
}
int Min[M];
int main(){
int n,m;Rd(n),Rd(m);
for(int i=1,x;i<=n;i++){
Rd(x);
sum[i]=sum[i-1]+x;
}
for(int _=1;_<=m;++_){
int k;Rd(k);
int ans=0;
for(int i=1;i<=n;i++)
if(sum[Min[i-1]]-1ll*Min[i-1]*k<=sum[i]-1ll*i*k)Min[i]=Min[i-1];
else Min[i]=i;
for(int i=1;i<=n;i++)
while(i-ans>=0&&sum[Min[i-ans]]-1ll*Min[i-ans]*k<=sum[i]-1ll*i*k)ans++;
printf("%d%c",ans-1,_==m?'\n':' ');
}
}