POI 2012 STU-Well

POI 2012 STU-Well

洛谷传送门

题意翻译

给定一个非负整数序列A,每次操作可以选择一个数然后减掉1,要求进行不超过m次操作使得存在一个A_k=0A**k=0且\max(|x_i-x_{i-1}|)max(∣x**ix**i−1∣)最小,输出这个最小值以及此时最小的k


题解:

首先看到最大值最小就考虑二分答案。

直接二分的就是最大斜率最小值。我们进行扫描,如果碰到比当前二分到的答案大的就把它变小,然后递减操作次数。因为是绝对值,所以要正反扫两遍。

之后我们考虑如何挑一个k使之得0。

我们发现,对于我们目前二分到的答案,如果有一个位置\((k,0)\),那么其可行区间一定是过这个点,斜率为二分到的答案的下面的部分。如果有点在上面,肯定是不符合题意的,要减下来。我们又发现,这样的点一定是连续的分布在当前枚举到的K的两侧。所以可以用一个类似于滑动窗口的东西来维护这里。碰到第一个符合题意的就跳出。

代码:

#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e6+6;
int n,m,ans;
int a[maxn],b[maxn],sum[maxn];
int check(int x)
{
    int tot=0;
    for(int i=1;i<=n;i++)
        b[i]=a[i];
    b[n+1]=-1;
    for(int i=2;i<=n;i++)
        if(b[i]-b[i-1]>x)
        {
            tot+=(b[i]-b[i-1]-x);
            b[i]=b[i-1]+x;
        }
    if(tot>m)
        return 0;
    for(int i=n-1;i>=1;i--)
        if(b[i]-b[i+1]>x)
        {
            tot+=(b[i]-b[i+1]-x);
            b[i]=b[i+1]+x;
        }
    if(tot>m)
        return 0;
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+b[i];
    int l=1,r=1;
    for(int i=1;i<=n;i++)
    {
        int tmp=b[i];
        b[i]=0;
        while(b[l]<x*(i-l))
            l++;
        while(b[r+1]>=x*(r-i+1))
            r++;
        b[i]=tmp;
        int all=(1ll*x*((1ll*(i-l)*(i-l+1)+1ll*(r-i)*(r-i+1))/2));
        if(m-tot>=(sum[r]-sum[l-1])-all)
            return i;
    }
    return 0;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    if(check(0))
    {
        printf("%lld %lld\n",check(0),0);
        return 0;
    }
    int l=0,r=1e9;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    printf("%lld %lld\n",check(r),r);
    return 0;
}
posted @ 2020-11-25 20:38  Seaway-Fu  阅读(59)  评论(0编辑  收藏  举报