题解 CF671B 【Robin Hood】

· · 题解

拿到题目不着急,抽象成一个数学问题后,看看到底问了什么,要求什么:

题意很清晰,一个序列操作 K 次,每次最大值 -1 后最小值 +1 ,求最终的 最大值-最小值 。

这种区间最大最小值还带修改的问题当然自然的想到线段树啦!

然后发现 k ≤ 1e9

。。。。。。

我们学校的一名大佬说过:

处理一个1e9的整数当然是二分啊!

于是我们想想这道题怎么二分!

首先想想二分什么:

既然答案是 Max - Min ,你总不能直接二分这个答案吧。

所以考虑让这个东西最小,只要让 Max 最小而 Min 最大就好了。

所以我们分别二分这两个值,由于道理是相同的,就只讲 Max 的部分了:

经过 K 轮劫富济贫后,剩下的人最多的钱数为 Max ,那么很明显:

1,初始钱数不大于 Max 的人都不会被劫

2,初始钱数大于 Max 的人都会被劫 A[i] - Max 次

所以只要 O(N) 扫一遍看看达成当前的 Max 值的话,K 天够不够。

边界处理要略小心,因为最大值显然不会低于平均值,不会多于最富的人的钱,所以 L = Num / N + ( Num % N != 0 ) , R = Max A

这个 L 的意思就是,整除的话就是平均值,否则就是平均值 +1 。

对于 Min 做一次一样的二分, L = 1 , R = Num / N 整不整除都没关系。

最后输出 Max - Min

那么这样做为什么是对的呢?

闭上眼睛,感性理解,回顾一下我们在做什么:

两遍二分,我们都是尽量让最小值最大,最大值最小。

那么这个主人公在干什么呢?

他每次抢最多的人的钱给最少的人,尽量减小最大值,增加最小值,所以和我们做的事情是一样的。

于是这个二分是对的。

#include <cstdio>
using namespace std ;

inline long long Max( long long A , long long B ) { return A > B ? A : B ; }
inline long long Readin() {

    register long long K = 0 ;
    register char C = getchar() ;

    for( ; C < '0' or C > '9' ; C = getchar() ) ;
    for( ; C <= '9' and C >= '0' ; C = getchar() )
        K = ( K << 1 ) + ( K << 3 ) + C - '0' ;

    return K ;

}

long long N , K ;
long long A[500005] ;
long long Sum ;
long long Arv ;

inline bool Check1( long long Num ) {

    long long Ans1 = 0 ;
    for(register long long i = 1 ; i <= N ; i++ )
        Ans1 += Max( 0 , Num - A[i] ) ;
    return Ans1 <= K ;

}

inline bool Check2( long long Num ) {

    long long Ans2 = 0 ;
    for(register long long i = 1 ; i <= N ; i++ )
        Ans2 += Max( 0 , A[i] - Num ) ;
    return Ans2 <= K ;

}

long long L , R , Mid ;
long long Maxx , Minn ;

int main() {

    N = Readin() ;
    K = Readin() ;

    for(register long long i = 1 ; i <= N ; i++ ) {
        Maxx = Max( Maxx , A[i] = Readin() ) ;
        Sum += A[i] ;
    }
    Arv = Sum / N ;

    Mid = ( L = 1 ) + ( R = Arv ) >> 1 ;
    for( ; L < R ; Mid = L + R + 1 >> 1 )
        Check1( Mid ) ? L = Mid : R = Mid - 1 ;
    Minn = L ;

    Mid = ( L = Arv + ( Sum % N != 0 ) ) + ( R = Maxx ) >> 1 ;
    for( ; L < R ; Mid = L + R >> 1 )
        Check2( Mid ) ? R = Mid : L = Mid + 1 ;
    Maxx = L ;

    return not printf( "%lld\n" , Maxx - Minn ) ;

} 

二分真是个好东西。