[POI 2012]Well(二分+单调性)

21 篇文章 0 订阅
8 篇文章 0 订阅

题目链接

http://main.edu.pl/en/archive/oi/19/stu

题目大意

给你一个长度为 n 的序列A,每次操作可以让其中一个数字减1,最多能进行 m 次操作,问要使得存在某个Ai=0的话, max{|AiAi+1|} 的最小值是多少

思路

我们可以二分答案,此问题变为判定性问题:问要使得存在某个 Ai=0 的话, max{|AiAi+1|} 是否可以小于等于 mid

我们首先要操作几次,让整个序列满足 max{|AiAi+1|} 小于等于 mid 。首先从左到右扫,若出现 AiAi1>mid 的情况, Ai 就要减去一部分。显然这样做之后,就能满足 max{Ai+1Ai|}mid 。然后从右到左扫,若出现 AiAi+1>mid 的情况, Ai 就要减去一部分。显然这样做之后,就能满足 max{AiAi+1|}mid 。两次操作后,就能满足 max{|AiAi+1|}mid

然后我们就需要让一个 Ai 变成0了,某个 Ai 变成0之后, Ai 附近会有连续的一段元素的数字大小都要减少,这个大概yy下可以想得到。记这段区间为 [Li,Ri] Li 显然应该满足: j<Li,Aj(ij)mid Ri 也差不多。这个感觉也比较容易想出来, [Li,Ri] 区间内的所有元素的数字大小都得减少,这个大家自己脑补下吧,我感觉有点难讲清楚。。。

这样,每次都重新找 [Li,Ri] ,总的复杂度是 O(n2logn)

但是可以发现,随着 i 的增加,Li显然是单调递增的,随着 i 的减小,Ri也显然是单调递减的,这样我们可以 O(n) 预处理出 [Li,Ri] ,总的复杂度是 O(nlogn)

代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值