【问题标题】:"The Trip" Programming Challenges“旅行”编程挑战
【发布时间】:2015-09-18 02:06:50
【问题描述】:

这就是问题所在。

一群学生是一个俱乐部的成员,该俱乐部每年都会前往不同的地点。他们过去的目的地包括印第安纳波利斯、凤凰城、纳什维尔、费城、圣何塞和亚特兰大。今年春天,他们计划去埃因霍温旅行。

小组事先同意平均分摊费用,但在发生时分摊每笔费用是不切实际的。因此,团体中的个人为特定的事物付费,例如膳食、酒店、出租车和机票。旅行结束后,计算每个学生的费用并兑换货币,以使每个学生的净成本相同,在 1 美分以内。过去,这种货币兑换既繁琐又耗时。您的工作是从费用清单中计算出必须转手的最低金额,以使所有学生的费用均等(在 1 美分以内)。

输入

标准输入将包含多次行程的信息。每次旅行由一行组成,其中包含一个正整数 n,表示旅行中的学生人数。接下来是 n 行输入,每行包含学生花费的金额,以美元和美分表示。学生人数不超过 1000 人,且没有学生花费超过 10,000.00 美元。包含 0 的单行跟随上次行程的信息。

输出

对于每次旅行,输出一行,说明必须兑换的总金额(以美元和美分表示)以平衡学生的成本。

示例输入

3
10.00
20.00
30.00
4
15.00
15.01
3.00
3.01
0

样本输出

$10.00
$11.99

我的代码适用于某些测试用例,但在其他用例中失败。我认为这是因为浮点数的精度错误。但是,我找不到错误。

例如,

输入: 4 9999.1 9999.1 9999.0 9999.1

输出: $0.06

但是,输出应该是 $0.07

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define MAX 1000

using namespace std;

float money[MAX];

int main(){
    int numOfStudents;
    int i; // loop counter
    double average; // of the costs
    double negDiff, posDiff; // |amount-average|
    double minDiff;
    float total; // all the costs added together
    
    while(scanf("%d", &numOfStudents) == 1){
        if(numOfStudents == 0){
            break;
        }
        
        memset(money, 0, sizeof(money));
        
        total = 0;
        for(i = 0; i < numOfStudents; i++){ // scan for the cost of each student - input into array
            double m;
            scanf("%lf", &m);
            money[i] = m;
            total += m;
        }
        average = total/numOfStudents;
        negDiff = 0;
        posDiff = 0;
        
        for(i = 0; i < numOfStudents; i++){ // find the difference between average and each cost -> add together
            if(money[i] > average){
                posDiff += (long) ((money[i] - average) * 100.0) / 100.0;
            }
            else{
                negDiff += (long) ((average - money[i]) * 100.0) / 100.0;
            }
        }
        
        minDiff = 0;
        
        if(posDiff > negDiff){ // find the minimum value for all to equal
            minDiff = negDiff;
        }
        else{
            minDiff = posDiff;
        }
        
        printf("$%.2lf\n", minDiff);
    }
    
    return 0;
}

【问题讨论】:

  • 在最后一个例子中,输出不应该是 $0.00 吗?
  • 使用 minDiff = (posDiff + negDiff) / 2.0; 而不是您的 minDiff 代码部分,我得到 0.07。顺便说一句,你想要 C 还是 C++?
  • @Beta (9999.1 + 9999.1 + 9999.1 + 9999.0)/4 = 9999.075 -> 9999.07
  • @deviantfan,为什么要平均正负差异? C++,拜托。
  • @blank 计算:不是平均的,而是减半的。首先,必要的资金流是与平均值的差值之和(将其分成两个总和根本无关紧要)。但是,从 A 人支付给 B 人的每一分钱都会影响两者,所以/2。 C++:你应该重写你的整个程序。

标签: c++ floating-point


【解决方案1】:

计算平均值只需要下降到美分剩余。这意味着我们将把 x 美分放在边池中,由于 x 将小于 n,最终即使你分配它,也只有少数人会得到,而且他们最多会比其他人多 1 美分。所以他们都在美分。

现在说了这么多。这是简单的代码

int main( )
{
    int n;
    cin >> n;
    while( n ) {
        vector<double> v;
        while( n-- ) {
            double x;
            cin >> x;
            v.push_back( x );
        }
        double avg = accumulate( begin(v), end(v), 0.0 ) / v.size();
        avg = ((int)(avg*100))/100.00;
        double exchange = 0;
        for ( auto x : v ) { if ( x < avg ){ exchange += avg - x; } }
        cout << exchange << endl;
        cin >> n;
    }
    return 0;
}

对于这个输入

3
10.00
20.00
30.00
4
15.00
15.01
3.00
3.01
4
9999.1
9999.1
9999.0
9999.1
0

输出是

10
11.99
0.07

【讨论】:

    【解决方案2】:

    嗯……你可能是对的。你真的不应该把钱读成浮点数。

    scanf ("%d.%d", &dollars, &pennies);
    int m = dollars * 100 + pennies;
    

    至于解决一般问题,请确保您坚持积分除法

    int average = total / numStudents;
    int leftover = total % numStudents;
    // numStudents - leftover students need to have paid $average
    // leftover students need to have paid $average + 1
    
    int paid = 0;
    int recieved = 0;
    for (int i = 0; i < numStudents; ++i)
    {
      // Since we start with the lowest amount owed, money array needs to have been 
      //   sorted such that the student who paid the least comes first.
      owed_money = average;
      if (i > numStudents - leftover)
        owed_money += 1;
    
      if (money[i] < owed_money)
        paid += owed_money - money[i];
      else if (money[i] > owed_money)
        recieved += money[i] - owed_money;
    }
    
    assert (paid == recieved);
    

    也许有更好的方法来做到这一点?无论如何,这是一个难题,但我可以保证您的解决方案不应包含 任何 浮点运算。

    【讨论】:

      【解决方案3】:

      如果您要将正负差值砍到小数点后 2 位,也请砍掉平均值。这给出了您期望的结果(简化的工作代码)

      #include <stdio.h>
      
      int main() {
          int n, i;
          double average, negDiff, posDiff, minDiff, total;
      
          while (scanf("%d", &n) == 1 && n != 0) {
              double money[n];
              total = 0.0;
              for (i = 0; i < n; i++) {
                  scanf("%lf", &money[i]);
                  total += money[i];
              }
              average = (long) ((total / n) * 100.0) / 100.0;
              negDiff = posDiff = 0.0;
              for (i = 0; i < n; i++) {
                  if (money[i] > average)
                      posDiff += (long) ((money[i] - average) * 100.0) / 100.0;
                  else
                      negDiff += (long) ((average - money[i]) * 100.0) / 100.0;
              }
              minDiff = posDiff > negDiff ? negDiff : posDiff;
              printf("$%.2lf\n", minDiff);
          }
          return 0;
      }
      

      输入

      3
      10.00 20.00 30.00
      4
      15.00 15.01 3.00 3.01
      4
      9999.1 9999.1 9999.0 9999.1
      0
      

      输出

      $10.00
      $11.99
      $0.07
      

      http://ideone.com/cxhvlL

      【讨论】: