蓝桥杯2018年A组-付账问题
0.题目
题目描述
几个人一起出去吃饭是常有的事。但在结帐的时候,常常会出现一些争执。
现在有 \(n\) 个人出去吃饭,他们总共消费了 \(S\) 元。其中第 \(i\) 个人带了 \(a_i\) 元。幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?
为了公平起见,我们希望在总付钱量恰好为 \(S\) 的前提下,最后每个人付的钱的标准差最小。这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 \(1\) 分钱的整数倍。你需要输出最小的标准差是多少。
标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。形式化地说,设第 \(i\) 个人付的钱为 \(b_i\) 元,那么标准差为 \(s=\sqrt{\frac{1}{n}\sum_{i=1}^n(b_i-\frac{1}{n}\sum_{i=1}^n b_i)}\)
输入格式
第一行包含两个整数 \(n\)、\(S\);
第二行包含 \(n\) 个非负整数 \(a_1,\cdots,a_n\)。
输出格式
输出到标准输出。
输出最小的标准差,四舍五入保留 \(4\) 位小数。
保证正确答案在加上或减去 \(10^{-9}\) 后不会导致四舍五入的结果发生变化。
样例 #1
样例输入 #1
5 2333
666 666 666 666 666
样例输出 #1
0.0000
样例 #2
样例输入 #2
10 30
2 1 4 7 4 8 3 6 4 7
样例输出 #2
0.7928
提示
【样例解释】
- 每个人都出 2333/5 元,标准差为 0。
【数据约定】
对于 \(10\%\) 的数据,所有 \(a_i\) 相等;
对于 \(30\%\) 的数据,所有非 \(0\) 的 \(a_i\) 相等;
对于 \(60\%\) 的数据,\(n \le 1000\);
对于 \(80\%\) 的数据,\(n \le 10^5\);
对于所有数据,\(n \le 5 \times 10^5,0 \le a_i \le 10^9\)。
1.题解
1.1 贪心
思路
这里有很多需要注意的点:
1.这里我们首先明确一点:最终计算的avg是固定的,即 S / n
2.我们主要弄清一个问题:每个人付款有多有少,谁多谁少,如何让这个标准差最小
3.使用贪心的思路: 首先我们为了逼近avg:
3.0首先我们对于整个数组进行排序,便于后续使用贪心算法(这里sort排序,注意1.我a数组从下标1开始; 2.第二个参数是结束位置的下一个位置)
3.1如果存在有些钱不够的人(a[i] < avg),这些人两种选择:付全款/付部分; 如果他们付部分,他们自身首先和avg形成的标准差更大;其次对于后面有富裕的人,他们本来可
以用更接近avg的钱即可付完全款,但是由于你少付了,他们都要多付,跟avg的标准差也就更大;所以对于这部分人我们选择全部付款
3.2出现第二种人,有富裕,但不多(a[i] >= avg), 这些人能付的钱已经大于平均值,但是由于前面有人少付了钱,这时候我支付avg是不够的,需要他们进行多付.又回到了这
个问题,他们是全付还是付部分即可? 我们可以利用之前的遍历,更新剩余需支付的S,计算出新的平均值(总人数 n-i+1)avg', 对于剩余这部分的人,如果持钱最少的人都多
于avg',那么对于后面钱更多的人,也是能付起avg'的,而且这时候整体最逼近于avg(接近avg的都尽可能全付,直到出现一个符合的avg'),剩余部分的人只需要付avg'的钱款
即可.
4.这里判断 a[i] >= avg', 并不是写作 a[i] >= S' / (n - i + 1) 而是写作乘法,避免精度确实的问题!!!
5.注意最后所有人不是付款当前人持有的a[i], 而是均支付 avg' 即可, 由于持有钱款不是连续, 可能当前 a[i] > avg'!!!
代码
#include<bits/stdc++.h>
#define ll long long
const int M = 5e5 + 1;
ll a[M];
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;
ll S;
cin >> n >> S;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a+1, a+n+1);
double avg = (S * 1.0) / n, differ = 0;
for(int i = 1; i <= n; i++) {
// 钱够了,全部支付
if (a[i] * (n - i + 1) >= S) {
double remain_avg = S * 1.0 / (n-i+1);
differ += (n - i + 1) * pow(remain_avg - avg, 2);
break;
} else {
S -= a[i];
differ += pow(a[i] - avg, 2);
}
}
differ = sqrt(differ / n);
printf("%.4lf", differ);
return 0;
}