未经许可,请匆转载!!!
公司从去年开始注重各级别工程师在算法编码方面的功底,不仅经常组织TopCoder考试,还把考试等级作为每年绩效考评的重要参考(鄙人对此非常反感)。不过反感归反感,但每次考试,如想让代码通过自动在线评测系统,还是得在算法上花一些功夫的。由于之前项目组顾老大非常支持个人练习(狂赞),特地用项目经费买了本编程挑战的书。书到项目组快一年多了,最近才有时间借来看看,也尝试着做书上的题,发现还是挺有趣的,能学到不少平时不注意的东西,也能提前写程序的技巧。由于鄙人做的第一题在提交4次后终于被系统AC,回头细想下,经常做做这些uva的算法题对自己各方面也是个锻炼,特此对每做的算法题做一总结。
读者如若发现分析或者代码有误,或者有更高效,更巧妙的算法,请留言或者联系作者,不胜感激!
contact information:
name: hugehwang E-mail:fantasynavy0817@126.com Tele-phone: no
原题描述:
有一群学生团体准备去国外旅游,他们都同意费用均摊,但是在值钱的时候却很不方便。于是有人先付车钱,有人先付餐费等等。等到旅游结束后,再统计每个人出了多少钱,然后出得少的拿钱给出钱多的人,使得每个人所出的钱都相等,如果无法使每个人出的钱相等,那就要求只能相差在一分钱之内。你现在的任务是:给你每个学生的旅游时出的费用,请你算出最少有多少钱要交换才能使每个人出的钱相等或在允许的误差之内。
输入数据:
每组测试数据的第一行有一个数,它代表本次旅游的总人数。接下来的n行为每个学生旅游时出的钱,最小的单位为一分钱。学生的总人数不会超过1000人,每个人出的钱不会超过$10000.00元。n=0时表示输入结束。
输出数据:
对每组数据输出一行,输出最少需要交换多少钱就能使得每个人所出的钱尽可能接近。 格式请参考如下sample.
sample input
3
10.00
20.00
30.00
4
15.00
15.01
3.00
3.01
5
5000.00
11.11
11.11
11.11
11.11
0
sample output
$10.00
$11.99
$3991.11
分析:
初次接触此题,很容易就会想到解法为求平均值。但此题在求平均值的基础上增加了条件,那就是误差可以在1美分之内,其实本质上也就是平均值可以在0~1之间浮动。现在假设平均值下限值记为avg【向下取整】,那么平均值的上限值则可以记为avg+1【向上取整】。为了使每个人出的钱都接近相等,那就必须让每个人出的钱都尽可能地在这里定义的两个平均值之间,那么出的钱少于这个平均值的人所补的钱之和或者出的钱高于这个平均值的人所收的钱之和就是我们想要计算的总的要交换的钱了。为了让交换的钱尽可以地少,那就必须让要收钱的人尽可以少收钱或者要出钱的人尽可能少出钱。于是,要收钱的人以avg+1为基准而要出钱的人以avg为基准。这样就解决了我们的问题了。
#include
#include
using namespace std;
int money[1000];
int main()
{
//freopen("H:\\algorithm_study\\uva_the_trip\\sample_input.txt", "r", stdin);
//person in trip
int persons;
while (cin >> persons && persons)
{
int total = 0;
//read money, in format of float
for (int i = 0; i < persons; ++i)
{
int a = 0, b = 0;
cin >> a;
cin.get();
cin >> b;
money[i] = a * 100 + b;
total += money[i];
}
//calculate average
int avg = total / persons;
int avg_add = avg + 1;
//calculate minmize
int remainder = 0;
int diff = 0;
int min = 0;
for (int i = 0; i < persons; ++i)
{
if (money[i] > avg_add)
{
diff = money[i] - avg_add;
remainder -= diff;
min += diff;
}
else if (money[i] < avg)
{
diff = avg - money[i];
remainder += diff;
}
}
if (remainder > 0)
{
min += remainder;
}
cout.setf(ios::fixed | ios::showpoint);
cout.precision(2);
cout << "$" << (double)min / 100 << endl;
}
return 0;
}
后记:
在解决此题的过程中,出现了5次在uva上提交不过的情况,最终发现是读取浮点数的方式不对。不过到现在我也没有明白为何先定义一个double型的变量d,然后再cin >> d;在uva上会不通过?在调试的过程中,发现如果要读取的浮点型数据是9999.99,那么d的值居然是9999.989999。怪哉!!!?
在网上也看了不少人的思路,很多人都是先由钱的总数整除总人数计算出一个平均值,其实这个是向下取整,然后以此平均值为基准计算大于这个平均值的人要收的钱的总数和少于这个平均值的人要出的钱的总数,再选出两个总数中的小的那个。由上面我们分析的可知,这种算法最小的不见得是正确的。另外,【以下见解纯属技术讨论,不含有任何诋毁、攻击等意图,如让读者产生误解,纯属意外!!!】对网上一个分析此题的想法印象特别深刻,大概是在这里所说的错误的思路的基础上再处理一个他认为是特殊的情况,数据由0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.03组成。虽然他给出的代码通过了uva的评测,但在我看来这种代码就是奔着结果去的,而且也不是很好理解。
算法的背后都是有着某种规律,可以由特殊推向一般,也可以由一般推向特殊。如果一种算法需要考虑对那些不具有特殊性的数据进行特殊处理的话,那么可以反证此算法是错误的。
以上文字纯属属个人研究所得,这里还是要感谢http://www.hankcs.com/program/uva-q10137-the-trip.html这篇文章的作者。让我正确的找到了平均的上限和下限,本以为下限是avg-1,上限是avg+1.
欢迎大家指正!!!