[Ioi2011] ricehub

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2600

这道题按说是当年较为水的一道题,但是我没有独立想出。

相当于是找出一个最长的序列使得所有的数字与中位数差值的绝对值之和(就是运费)最小。

基本的思路是二分答案,因为如果存在长度为x的一个序列的话,一定也存在长度为x-1的一个序列。

关键就是O(n)检验二分出的答案。

可以发现通过分类讨论是可以快速统计所有的长度为L的运费的。

假设二分出的长度为len。

初始有一个区间 [1,len] 然后每一次只要从 [l,r] 转移到 [l+1,r+1] 即可。

很好转移,相当于先删除掉 l 这个位置上的数字与中位数的差值,而后加上 r 这个位置的数与中位数的差值。

中位数的变化是1或者0,加上(mid-l+1)减去(r-mid)即可。

#include <cstdio>
#include <cstring>
#include <algorithm>

#define LL long long
#define N 400010

using namespace std;

int n;
LL L,B,x[N];

inline LL Abs(LL x){
    if(x<0) return -x;
    return x;
}

inline bool check(int len){
    LL ans=0;
    for(int i=1;i<=len;i++)
        ans+=Abs(x[(len+1+1)>>1]-x[i]);
    if(ans<=B) return 1;
    int mid=(len+1+1)>>1;
    for(int l=2;l<=n-len+1;l++){
        int r=l+len-1;
        ans-=x[mid]-x[l-1];
        mid=(l+r+1)>>1;
        ans+=(mid-l)*(x[mid]-x[mid-1]);
        ans-=(r-mid)*(x[mid]-x[mid-1]);
        ans+=x[r]-x[mid];
        if(ans<=B) return 1;
    }
    return 0;
}

int main(){
    scanf("%d%lld%lld",&n,&L,&B);
    for(int i=1;i<=n;i++) scanf("%lld",&x[i]);
    int l=1,r=n;
    while(r-l>10){
        int mid=(l+r)>>1;
        if(check(mid)) l=mid;
        else r=mid;
    }
    for(;r>=l;r--) if(check(r)) break;
    printf("%d
",r);
    return 0;
}
Code
原文地址:https://www.cnblogs.com/lawyer/p/4548443.html