☰
UVa 119题解:哈希表在礼物分配算法中的应用与实践
2026/10/28 13:40:14 网站建设 项目流程

1. 题目背景与需求解析

UVa 119 "Greedy Gift Givers" 是国际大学生程序设计竞赛(ICPC)和各类编程比赛中常见的模拟类练习题。这道题目源自真实生活中的礼物赠送场景,考察选手对基础数据结构的运用和边界条件的处理能力。

题目描述了一群人互相赠送礼物的过程,要求根据每个人的初始金额、赠送对象和金额分配情况,计算出最终每个人的净收入或支出。这类问题在金融结算、社交网络分析等领域都有实际应用价值。

关键提示:题目虽然逻辑简单,但隐藏了许多新手容易忽略的细节陷阱,比如零钱分配、同名处理等边界情况。

2. 数据结构设计与算法选择

2.1 输入输出分析

输入格式通常为:

N(人数) 姓名1 姓名2 ... 姓名N 后续N组数据: 赠送者姓名 初始金额 赠送人数 接收者1 接收者2 ... 接收者M

输出要求显示每个人的净变化:

姓名1 净变化 姓名2 净变化 ... 姓名N 净变化

2.2 核心数据结构

推荐使用两种数据结构组合:

  1. 哈希表/字典:存储姓名到账户余额的映射
    • C++:unordered_map<string, int>
    • Python:dict[str, int]
  2. 动态数组:保存原始姓名顺序
    • 用于保证输出顺序与输入一致
// C++示例结构 unordered_map<string, int> balance; vector<string> names;

2.3 算法流程

  1. 读取所有参与者姓名,初始化余额为0
  2. 处理每个赠送者:
    • 读取初始金额和赠送人数
    • 计算每人分得金额(注意整数除法)
    • 赠送者账户扣除总支出
    • 每个接收者账户增加相应金额
  3. 按原始顺序输出结果

3. 关键实现细节与陷阱

3.1 零钱处理问题

题目要求"no fractional money",意味着必须使用整数除法。常见错误包括:

# 错误做法(产生浮点数) each = amount / num_recipients # 正确做法(整数除法) each = amount // num_recipients

重要细节:当赠送人数为0时,应跳过分配步骤,避免除零错误

3.2 赠送金额计算

赠送者实际支出应为each * num_recipients,而不是初始金额。因为:

  • 初始金额可能无法被人数整除
  • 余数应保留在赠送者账户中
if num_recipients > 0: each = amount // num_recipients balance[giver] -= each * num_recipients # 不是 -= amount

3.3 输入输出规范

  • 每组数据后需要输出空行(包括最后一组)
  • 姓名区分大小写
  • 初始金额可能是0
  • 赠送人数可能为0(即不赠送任何人)

4. 完整代码实现与解析

4.1 Python实现

def solve(): first_case = True while True: try: n = int(input()) except EOFError: break if not first_case: print() first_case = False names = input().split() balance = {name: 0 for name in names} for _ in range(n): giver = input().strip() amount, m = map(int, input().split()) recipients = input().split() if m > 0 else [] if m == 0: continue each = amount // m balance[giver] -= each * m for recipient in recipients: balance[recipient] += each for name in names: print(f"{name} {balance[name]}") if __name__ == "__main__": solve()

4.2 C++实现

#include <iostream> #include <vector> #include <unordered_map> using namespace std; void solve() { int n; bool first_case = true; while (cin >> n) { if (!first_case) cout << endl; first_case = false; vector<string> names(n); unordered_map<string, int> balance; for (int i = 0; i < n; ++i) { cin >> names[i]; balance[names[i]] = 0; } for (int i = 0; i < n; ++i) { string giver; int amount, m; cin >> giver >> amount >> m; if (m == 0) continue; int each = amount / m; balance[giver] -= each * m; for (int j = 0; j < m; ++j) { string recipient; cin >> recipient; balance[recipient] += each; } } for (const auto& name : names) { cout << name << " " << balance[name] << endl; } } } int main() { solve(); return 0; }

5. 测试用例与调试技巧

5.1 典型测试用例

基础案例:

5 dave laura owen vick amr dave 200 3 laura owen vick owen 500 1 dave amr 150 2 vick owen laura 0 2 amr vick vick 0 0

边界案例:

3 a b c a 100 0 b 50 1 a c 0 3 a b c

5.2 调试技巧

  1. 使用小规模数据手动验证
  2. 打印中间变量:
    • 每次分配前后的账户余额
    • 计算出的each值
  3. 特别注意:
    • 除零情况
    • 最后是否多输出空行
    • 余额计算是否使用整数除法

6. 算法复杂度分析

时间复杂度:O(N + M)

  • N:人数
  • M:所有赠送操作中的接收者总数

空间复杂度:O(N)

  • 存储姓名和余额的映射

在实际竞赛中,该算法可以轻松处理题目给出的约束条件(通常N≤10)。对于大规模数据(如N≤1e5),可能需要更高效的IO处理方式。

7. 实际应用扩展

虽然题目看似简单,但其核心逻辑可以扩展到:

  1. 社交网络中的资金流转分析
  2. 团体活动费用分摊计算
  3. 区块链交易模拟
  4. 财务系统中的往来账目核对

我在实际开发中曾用类似逻辑处理过电商平台的优惠券分发系统,关键点同样在于正确处理整数分配和边界情况。一个经验是:当涉及金额分配时,务必使用专门的十进制数据类型(如Python的Decimal),避免浮点数精度问题。

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

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

立即咨询