付账问题
付账问题
几个人一起出去吃饭是常有的事。
但在结帐的时候,常常会出现一些争执。
现在有 个人出去吃饭,他们总共消费了 元。
其中第 个人带了 元。
幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?
为了公平起见,我们希望在总付钱量恰好为 的前提下,最后每个人付的钱的标准差最小。
这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 分钱的整数倍。
你需要输出最小的标准差是多少。
标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。
形式化地说,设第 个人付的钱为 元,那么标准差为 :
输入格式
第一行包含两个整数 、;
第二行包含 个非负整数 。
输出格式
输出最小的标准差,四舍五入保留 位小数。
数据范围
,
,
。
输入样例1:
5 2333 666 666 666 666 666
输出样例1:
0.0000
输入样例2:
10 30 2 1 4 7 4 8 3 6 4 7
输出样例2:
0.7928
解题思路
我们的目的是使得方差最小化。
我们的贪心策略是每个人付的钱应该尽可能平均,这样方差才小:
- 如果每一个,则每一个都取。
- 如果某一个,则对应的,有多少给多少,少给的钱则分摊给其他钱数比他多的人。
下面证明一下这种做法是对的。
首先有不等式当时取等号,即取到最小值。
在中的对应于方差公式中的。我们把每一个都加起来,得到因此有且当,即取最小值0。证明了第一种情况,当每一个,。
然后对于第二种情况,如果某一个,用反证法,对应的取一个小于的值(的值不可能大于),会更小。我们可以找到一个满足,我们知道当与相差很小时,才会变小,而又因为,这意味着我们可以把继续取大到,使得与的差变小,从而变得更小,矛盾。
具体的做法是,先将排序,从前往后遍历,如果,那么必然取,同时剩下的人还要尽可能分摊没付的钱。对于公式已经固定了,能变化的只有后面的人,但后面的人要交的钱也是固定的,,通过均值不等式,我们发现从往后每一个取,才能使得最小。剩下的分析同理,与贪心策略一样,如果发现某一个大于当前要分的钱的平均数,那么就按照情况一来处理。
当时写了一个错误代码,思路是把小于等于当前要分的钱的平均数的人找出来,然后这些人都各种交,把剩余的钱都交给后面的人分,与上面的方法不同的是,我的方法是一段一段分的,效果肯定不如上面的一个一个人分的好。

#include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int N = 5e5 + 10; const double eps = 1e-8; double a[N], b[N]; int find(int left, int right, double val) { while (left < right) { int mid = left + right >> 1; if (a[mid] - val > eps) right = mid; else left = mid + 1; } return left; } void dfs(int left, int right, double s) { double avg = s / (right - left + 1); int n = find(left, right, avg); if (n == left) { for (int i = left; i <= right; i++) { b[i] += avg; } return; } for (int i = left; i < n; i++) { b[i] += a[i]; s -= a[i]; } dfs(n, right, s); } int main() { int n; double s; scanf("%d %lf", &n, &s); for (int i = 0; i < n; i++) { scanf("%lf", a + i); } sort(a, a + n); dfs(0, n - 1, s); double ret = 0, avg = s / n; for (int i = 0; i < n; i++) { ret += (b[i] - avg) * (b[i] - avg); } ret = sqrt(ret / n); printf("%.4f", ret); return 0; }
AC代码如下:
1 #include <cstdio> 2 #include <cmath> 3 #include <algorithm> 4 using namespace std; 5 6 const int N = 5e5 + 10; 7 const double eps = 1e-6; 8 9 int a[N]; 10 11 int main() { 12 int n; 13 double s; 14 scanf("%d %lf", &n, &s); 15 for (int i = 0; i < n; i++) { 16 scanf("%d", a + i); 17 } 18 19 sort(a, a + n); 20 21 long double ret = 0, avg = s / n; 22 for (int i = 0; i < n; i++) { 23 double t = s / (n - i); // 当前要分的钱的平均数 24 if (a[i] - t < eps) t = a[i]; // 如果当前的人的钱小于平均数,则他要交的钱是a[i],否则要交的钱就是平均数 25 ret += (t - avg) * (t - avg); 26 s -= t; 27 } 28 printf("%.4llf", sqrt(ret / n)); 29 30 return 0; 31 }
参考资料
AcWing 1235. 付账问题(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/734/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/15952177.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探