「数学」付账问题

本题蓝桥OJ第174题的题解(蓝桥OJ上的相同题解也是我发的)

题面

题目描述

几个人一起出去吃饭是常有的事。但在结帐的时候,常常会出现一些争执。现在有n个人出去吃饭,他们总共消费了S元。其中第i个人带了 \(a_i\) 元。幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?为了公平起见,我们希望在总付钱量恰好为S的前提下,最后每个人付的钱的标准差最小。这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是1分钱的整数倍。你需要输出最小的标准差是多少。

标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的"偏差有多大"。形式化地说,设第i个人付的钱为 $ b_i $元,那么标准差为: $ \sqrt{\frac{1}{n}\sum_{i=1}{n}(b_i-\frac{1}{n}\sum_{i=1})^2b_i}$​

输入

第一行包含两个整数n、S;

第二行包含n个非负整数 \(a_1,\dots,a_n\)
其中, \(n \leq 5 \times 10^5\), \(0 \leq a_i \leq 10^9\)

输出

输出最小的标准差,四舍五入保留4位小数。

保证正确答案在加上或减去 \(10_{-9}\) 后不会导致四舍五入的结果发生变化。

样例输入

10 30
2 1 4 7 4 8 3 6 4 7

样例输出

0.7928

思路分析

想要标准差变小,只需要让每个人付的钱足够接近即可.显然,如果每个人都付平均数的钱,那标准差就是最小的0.

但是实际上会普遍存在有人钱不够付平均数的情况,这时这个人只能把他有的钱全付了,而他少付的钱只能由钱比他多的人来付.显然,想让这种情况下的标准差变小,这个少付的钱也需要平摊(每个人付平均数),即在原本平摊总额平均数的同时再平摊前面人少付的钱.这也相当于说,后面的人需要付当前还需要付的钱的总额的平均数.

如果后面的人的钱不够付这个新的平均数,那么同样处理,后面的人继续平摊少付的那部分.

所以我们只需要将所有人有的钱升序排序,这样可以始终先处理钱不够的人.我们不断维护剩下需要付的钱的总额,然后累加方差中的那个累加式即可.当有人的钱购付时,由于已排序,后面的人一定也购付,此时后面的人全部支付当前平均数的钱,直接统一计算即可.

参考代码

时间复杂度: \(O(nlogn)\)

空间复杂度: \(O(n)\)

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    i64 n, s;
    cin >> n >> s;

    int *a = new int[n];
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    sort(a, a + n);

    double ave = (double)s / n;  // 计算标准差时所需要使用的所有人付钱总额的平均数
    double sum = 0; // 方差中累加式的结果
    for (int i = 0; i < n; i++) {
        if (a[i] * (n - i) < s) {  // 不足支付当前平摊后的平均数
            sum += (a[i] - ave) * (a[i] - ave);  // 把此人有的钱全部支付
            s -= a[i];
        } else {  // 从当前人开始,够支付当前平摊后的平均数
            double money = (double)s / (n - i); // 当前平摊后的平均数,即当前人开始所有人付的钱
            sum += (money - ave) * (money - ave) * (n - i); // 直接统一计算
            break;
        }
    }

    cout << fixed << setprecision(4) << sqrt(sum / n);

    delete[] a;

    return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

热门相关:洪荒二郎传   锦庭娇   富贵不能吟   富贵不能吟   裙上之臣