题目链接
http://main.edu.pl/en/archive/oi/19/stu
题目大意
给你一个长度为
n
的序列
思路
我们可以二分答案,此问题变为判定性问题:问要使得存在某个 Ai=0 的话, max{|Ai−Ai+1|} 是否可以小于等于 mid
我们首先要操作几次,让整个序列满足 max{|Ai−Ai+1|} 小于等于 mid 。首先从左到右扫,若出现 Ai−Ai−1>mid 的情况, Ai 就要减去一部分。显然这样做之后,就能满足 max{Ai+1−Ai|}≤mid 。然后从右到左扫,若出现 Ai−Ai+1>mid 的情况, Ai 就要减去一部分。显然这样做之后,就能满足 max{Ai−Ai+1|}≤mid 。两次操作后,就能满足 max{|Ai−Ai+1|}≤mid
然后我们就需要让一个 Ai 变成0了,某个 Ai 变成0之后, Ai 附近会有连续的一段元素的数字大小都要减少,这个大概yy下可以想得到。记这段区间为 [Li,Ri] , Li 显然应该满足: ∀j<Li,Aj≤(i−j)mid , Ri 也差不多。这个感觉也比较容易想出来, [Li,Ri] 区间内的所有元素的数字大小都得减少,这个大家自己脑补下吧,我感觉有点难讲清楚。。。
这样,每次都重新找 [Li,Ri] ,总的复杂度是 O(n2logn) 。
但是可以发现,随着
i
的增加,
代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1100000
using namespace std;
typedef long long int LL;
int n,a[MAXN],b[MAXN];
int L[MAXN],R[MAXN],pos;
LL sum[MAXN],m;
bool check(LL limit) //判断max{|Ai-A{i+1}|}<=limit是否可能
{
LL cost=0;
for(int i=1;i<=n;i++) b[i]=a[i];
for(int i=2;i<=n;i++)
if(b[i]-b[i-1]>limit)
{
cost+=b[i]-b[i-1]-limit;
b[i]=b[i-1]+limit;
}
for(int i=n-1;i>=1;i--)
if(b[i]-b[i+1]>limit)
{
cost+=b[i]-b[i+1]-limit;
b[i]=b[i+1]+limit;
}
if(cost>m) return false;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+b[i];
for(int i=1,j=1;i<=n;i++) //求L[]
{
while(b[j]<(LL)(i-j)*limit) j++;
L[i]=j;
}
for(int i=n,j=n;i>=1;i--) //求R[]
{
while(b[j]<(LL)(j-i)*limit) j--;
R[i]=j;
}
for(int i=1;i<=n;i++)
{
LL tmp=sum[R[i]]-sum[L[i]];
tmp-=(LL)limit*(i-L[i])*(i-L[i]+1)/2;
tmp-=(LL)limit*(R[i]-i)*(R[i]-i+1)/2;
if(cost+tmp<=m)
{
pos=i;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int lowerBound=0,upperBound=1000000000,ans=-1;
while(lowerBound<=upperBound)
{
int mid=(lowerBound+upperBound)>>1;
if(check(mid))
{
upperBound=mid-1;
ans=mid;
}
else lowerBound=mid+1;
}
check(ans);
printf("%d %d\n",pos,ans);
return 0;
}