☰
UVa 119题解:模拟礼物交换的算法实现与边界处理
2026/10/26 20:16:51 网站建设 项目流程

1. 问题背景与题目解析

UVa 119 "Greedy Gift Givers" 是UVa Online Judge上的一道经典编程练习题,属于初级模拟类题目。这道题最早出现在1996年的USACO训练题库中,后来成为许多编程竞赛选手的入门必做题。

题目模拟了一个简单的礼物交换场景:一群人互相赠送礼物,每个人最初有一定金额的钱,按照特定规则给其他人赠送礼物后,需要计算每个人最终的净金额变化。这类模拟题在编程竞赛中非常常见,主要考察选手对问题描述的准确理解、边界条件的处理能力以及基础编程实现技巧。

提示:虽然题目表面简单,但实际提交时通过率不足40%,主要问题都出在输入输出格式处理和极端情况考虑上。

2. 题目要求详细拆解

2.1 输入输出规范

输入由多组测试用例组成,每组用例包含:

  1. 第一行是整数NP(2 ≤ NP ≤ 10),表示参与人数
  2. 接下来NP行,每行是一个参与者的名字(不超过12个字符)
  3. 然后对于每个参与者,按顺序给出:
    • 赠送者的名字
    • 赠送的总金额(0 ≤ 金额 ≤ 2000)
    • 赠送人数NG(如果NG > 0,则后面跟着NG个接收者的名字)

输出要求:

  • 对于每个参与者,按照输入顺序输出"名字 净金额"
  • 净金额 = 收到的总金额 - 送出的总金额
  • 每组测试用例输出后跟一个空行

2.2 核心算法逻辑

  1. 初始化阶段:

    • 读取所有参与者姓名,建立名字到金额的映射
    • 初始金额都设为0
  2. 处理每个赠送者:

    • 读取赠送者的名字、总金额和接收者列表
    • 计算每人应得金额 = 总金额 / 接收者人数(整数除法)
    • 赠送者送出金额 = 每人应得金额 * 接收者人数
    • 每个接收者收到相应金额
  3. 输出阶段:

    • 按原始顺序计算并输出每个人的净金额

2.3 关键边界条件

  • 当NG=0时,不应进行任何金额分配(避免除零错误)
  • 赠送金额为0时,无论接收人数多少都不应分配金额
  • 名字区分大小写,必须精确匹配
  • 输出必须严格保持原始输入顺序
  • 每组输出后必须有一个空行(包括最后一组)

3. 实现方案与代码解析

3.1 数据结构选择

最合适的实现方式是使用:

  • 一个vector或数组保存原始顺序的参与者名字
  • 一个unordered_map(或map)建立名字到净金额的映射
  • 一个临时变量保存当前处理者的信息
#include <iostream> #include <vector> #include <unordered_map> #include <string> using namespace std; int main() { int NP; bool first_case = true; while (cin >> NP) { if (!first_case) cout << endl; first_case = false; vector<string> names(NP); unordered_map<string, int> balance; for (int i = 0; i < NP; ++i) { cin >> names[i]; balance[names[i]] = 0; } for (int i = 0; i < NP; ++i) { string giver; int amount, NG; cin >> giver >> amount >> NG; if (NG == 0) continue; int gift = amount / NG; balance[giver] -= gift * NG; for (int j = 0; j < NG; ++j) { string receiver; cin >> receiver; balance[receiver] += gift; } } for (const auto& name : names) { cout << name << " " << balance[name] << endl; } } return 0; }

3.2 关键实现细节

  1. 输入处理:

    • 使用while循环处理多组测试用例
    • 使用first_case标志控制空行输出,确保最后一组后也有空行
  2. 金额计算:

    • 整数除法自动向下取整,符合题目要求
    • 先计算总送出金额,再逐个增加接收者金额
  3. 输出顺序:

    • 使用vector保存原始顺序,确保输出与输入顺序一致

4. 常见错误与调试技巧

4.1 典型错误模式

  1. 输出格式错误:

    • 忘记在每组输出后加空行
    • 最后一组也必须有空行
    • 输出顺序与输入不一致
  2. 边界条件处理不足:

    • NG=0时出现除零错误
    • 赠送金额为0时错误分配
    • 名字大小写不匹配
  3. 金额计算错误:

    • 未使用整数除法
    • 赠送者金额扣除不正确
    • 接收者金额增加不正确

4.2 调试建议

  1. 使用小型测试用例:
2 Alice Bob Alice 100 1 Bob Bob 0 0

预期输出:

Alice -100 Bob 100
  1. 极端情况测试:
  • 最大NP=10
  • 金额为0
  • NG=0
  • 名字长度刚好12字符
  1. 使用printf调试:
cerr << "Processing " << giver << " giving " << amount << " to " << NG << " people" << endl;

5. 算法优化与变种思考

5.1 性能优化

虽然本题数据量很小,不需要优化,但可以考虑:

  • 使用数组代替vector和map,通过索引访问
  • 提前计算好所有输入,再统一处理

5.2 题目变种

  1. 如果赠送金额不是平均分配,而是按特定比例?
  2. 如果允许赠送金额为负数(借贷)?
  3. 如果参与人数NP很大(如1e5级别)?
  4. 如果名字很长(如100字符)?

5.3 实际应用扩展

这类问题在实际中可用于:

  • 财务记账系统
  • 团体活动费用分摊
  • 社交网络中的虚拟货币流通模拟

6. 在线评测注意事项

  1. UVa Online Judge的特殊要求:

    • 必须使用文件结束符(EOF)判断输入结束
    • 输出必须完全匹配,包括空格和换行
    • 使用\n而不是endl可能更快(减少flush)
  2. 提交前的检查清单:

    • 所有边界条件是否处理?
    • 输出格式是否正确?
    • 变量是否初始化?
    • 输入输出是否同步(cin/cout与scanf/printf混用问题)?
  3. 评测结果解读:

    • WA (Wrong Answer):检查输出格式和逻辑
    • RE (Runtime Error):检查数组越界和除零
    • TLE (Time Limit):虽然本题不太可能,检查是否有死循环

7. 个人实现心得

在实际解决这个问题时,我最初犯了两个典型错误:一是忽略了NG=0的情况,导致除零错误;二是没有保持原始输出顺序,直接遍历map导致顺序错乱。调试过程中,我总结了几个有用的技巧:

  1. 先写输入输出框架,确保格式正确
  2. 处理每个赠送者时,先打印中间状态验证
  3. 使用小型测试用例手动计算预期结果
  4. 特别注意题目中的"每组输出后跟一个空行"要求

这道题虽然简单,但很好地训练了对问题描述的精确理解和边界条件的全面考虑能力。在编程竞赛中,这类模拟题往往是区分新手和有经验选手的关键——不是算法复杂度,而是对细节的把握。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询