「数学」付账问题
本题蓝桥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;
}
"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德
这里是浙江理工大学22届ACM集训队的成员一枚鸭!
本文首发于博客园,作者:星双子,除了我自己的转载请注明原文链接:https://www.cnblogs.com/geministar/p/lanqiao_174.html