[数论]中南大学 2013 校赛 G 题解题报告

Balls and Boxes

题意


微笑N 个盒子,原本有一些球;每次可以向 M 个不同的盒子里面各放一个球,问最少要多少次操作能让盒子里面的球数量相等。不可以输出 -1.

解法:


一开始写了一个姿势 wa了,换了一个姿势就过了
方法是——记录所有盒子的最大值 Max , 和 Sum  , 最小值 Min。 
那么如果最后能够达成,高度为H , 那么肯定满足以下三个条件
1、H >= Max
2、(n * H - Sum) % m == 0
3、要做的操作数T = (n * H - Sum) / m , 那么 T >= H - Min【重要!!!】
那么要做的事情就是首先解第二个方程,可以将他转化成最简单的 ax = b (mod n) 形式——
(n * H) = (m - Sum % m) % m (mod m)
解这个方程,首先假设 A * x + B * m = C ,用 extendGCD 求出来一组解和C 。 那么如果 (m - Sum % m) % C != 0  (或者是 Sum mod GCD(n , m) != 0) 的话一定没解。。
其次,再讲问题转化一下,我们求出来最小的 Hmin , 那么 H 的解就可以转化成 H + i * m / C ....巧合的是,我们之前用 extendGCD 已经求出来了一个 n 但是 n 可能是负数。
那么就每次加上 m / C 一直到 n >=  Max 为止。
但是这就是答案了么?明显不是,看下面这组数据
3
0 100000 100000
明显答案是 200000 , 不是 50000 。 那怎么办呢?就要用到第 3 个性质
方法是 二分。假设答案是 Hmin + ans * m / C . 那么枚举上下界二分即可,要求满足的是 T >= H + i * m / C 

代码

LL n , m;
LL a[20000];
void solve(){
    RD(n , m);
    LL s = 0 , h = 0 , l = 1e9;
    REP(i ,  n) {
        RD(a[i]);
        checkMax(h , a[i]);
        checkMin(l , a[i]);
        s += a[i];
    }
    LL d = GCD(n , m);
    if (s % d != 0){
        puts("-1");
        return;
    }
    LL x , y;
    d = extendGCD(n , m , x , y);
    x *= s / d;
    LL inc = m / d;
    LL ins = (h - x) / inc;
    x += ins * inc;
    if (x < h) x += inc;
//    cout << inc << endl;
    LL low = 0 , high = 1e9 , mid , ans = 1e9;
    do{
        mid = low + high >> 1;
        LL xx = x + inc * mid;
        LL t = (xx * n - s) / m;
        if (xx - l <= t){
            checkMin(ans , mid);
            high = mid - 1;
        }
        else low = mid + 1;
    }while(low <= high);
    printf("%lld\n" , ((x + inc * ans) * n - s) / m);
}
int main(){
//    freopen("0.txt" , "r" , stdin);
    Rush solve();
}


你可能感兴趣的:([数论]中南大学 2013 校赛 G 题解题报告)