[POI2012]STU-Well(二分答案+神仙操作)

给定一个非负整数序列A,每次操作可以选择一个数然后减掉1,要求进行不超过m次操作使得存在一个Ak=0且max{|Ai−Ai+1|}最小,输出这个最小lk以及最小值。

Solution

最大值最小,显然是需要二分的,而且我们发现答案确实是有单调性的。

接下来我们正着扫一遍整个序列,把差值大于二分出来的值的数调整值合法。

接下来我们就要考虑ak=0的情况,这个东西并没有什么单调性,我们只能从头枚举,直到找到第一个满足要求的就可以了。

考虑当一个数变成零之后,为了满足我们二分出来的答案,我们需要构造这样一个东西。

0                      0

0 0                0 0

0 0 0          0 0 0

0 0 0 0    0 0 0 0

0 0 0 0 0 0 0 0 0

两边是等差数列,为了计算序列的长度,我们需要计算出两个数组表示当ai=0是等差数列的左端点和右端点。

我们可以把左右分开算。

比如计算右端点,我们从i向右暴力扩展,当发现当前数字的值小于目标值是就不再扩展了,因为后面已经符合要求了。

最后枚举点,用等差数列求和算一下就可以了。

Code

#include<iostream>
#include<cstdio>
#define N 1000002
using namespace std;
typedef long long ll;
ll ans2,ans,b[N],s1[N],le[N],ri[N],a[N],n,m,sum[N];
bool check(ll pos){
    ll num=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]>pos)num+=b[i]-b[i-1]-pos,b[i]=b[i-1]+pos;
    if(num>m)return 0;
    for(int i=n-1;i>=1;--i)
      if(b[i]-b[i+1]>pos)num+=b[i]-b[i+1]-pos,b[i]=b[i+1]+pos;
    if(num>m)return 0;
    for(int i=1;i<=n;++i)sum[i]=sum[i-1]+b[i];
    for(int i=1,j=1;i<=n;++i){
         while(b[j]<=(i-j)*pos&&j<i)j++;
         le[i]=j;
    }
    for(int i=n,j=n;i>=1;--i){
        while(b[j]<=(j-i)*pos&&j>i)j--;
         ri[i]=j;
    }
    for(int i=1;i<=n;++i)
         if((num+sum[ri[i]]-sum[le[i]-1]-pos*((i-le[i])*(i-le[i]+1)+(ri[i]-i)*(ri[i]-i+1))/2)<=m)
         {ans=i;return 1;} 
    return 0;
}
int main(){
    scanf("%lld%lld",&n,&m);    
    ll l=0,r=1e9;
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans2=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%lld %lld",ans,ans2);
    return 0; 
} 

 

转载于:https://www.cnblogs.com/ZH-comld/p/9677644.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值