找回密码
 立即注册
查看: 19|回复: 0

2023年 GESP六级 小杨的握手问题的优雅解法:树状数组实战

[复制链接]
  • 打卡等级:即来则安
  • 打卡总天数:17
  • 打卡月天数:1
  • 打卡总奖励:185
  • 最近打卡:2025-08-06 09:29:10

21

主题

8

回帖

1万

积分

VIP

积分
10208
发表于 2025-7-19 09:59:40 | 显示全部楼层 |阅读模式
截图未命名.jpg
一、问题背景与算法选择
题目要求计算n个人按照特定顺序排队时发生的握手次数,本质上是计算序列中逆序对的数量。树状数组(Fenwick Tree)因其高效的区间查询和单点更新能力(O(logn))成为解决此类问题的理想选择。
二、完整代码实现(带详细注释)
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;

  4. // 树状数组实现类
  5. class FenwickTree {
  6. private:
  7. vector tree; // 存储树状数组
  8. int size; // 数组大小
  9. public:
  10. // 构造函数,初始化大小为n的树状数组
  11. FenwickTree(int n) : size(n), tree(n + 1, 0) {}
  12. // 更新操作:在index位置增加delta
  13. void update(int index, int delta) {
  14. // 典型的树状数组更新方式
  15. for(; index <= size; index += index & -index)
  16. tree[index] += delta;
  17. }

  18. // 查询操作:求前index个元素的和
  19. int query(int index) {
  20. int res = 0;
  21. // 典型的树状数组查询方式
  22. for(; index > 0; index -= index & -index)
  23. res += tree[index];
  24. return res;
  25. }
  26. };
  27. int main() {
  28. // 优化输入输出
  29. ios::sync_with_stdio(false);
  30. cin.tie(nullptr);
  31. int N;
  32. cin >> N;
  33. vector<int> order(N);
  34. for(int i = 0; i < N; i++) {
  35. cin >> order[i];
  36. order[i]++; // 转换为1-based索引
  37. }

  38. // 初始化树状数组
  39. FenwickTree ft(N);
  40. long long handshakes = 0;

  41. // 核心计算逻辑
  42. for(int i = 0; i < N; i++) {
  43. int current = order[i];
  44. // 查询比current小的已存在数的个数
  45. handshakes += ft.query(current - 1);
  46. // 将当前数加入树状数组
  47. ft.update(current, 1);
  48. }

  49. cout << handshakes << endl;
  50. return 0;
  51. }
复制代码


三、算法核心解析
  • 树状数组原理

    • 利用二进制索引高效维护前缀和
    • update和query操作的时间复杂度均为O(logn)
    • 空间复杂度O(n)

  • 1-based转换

    • 将输入值+1转换为1-based索引
    • 避免处理0索引带来的边界问题


    • 按顺序处理每个人时
    • 查询已处理人中编号比当前小的数量
    • 累加得到总握手次数

四、复杂度分析与优化
  • 时间复杂度

    • 预处理:O(n)
    • n次查询和更新:O(nlogn)
    • 总复杂度:O(nlogn)

  • 空间复杂度

    • 树状数组:O(n)
    • 输入存储:O(n)

  • 优化方向

五、常见问题解答
Q:为什么选择树状数组而不是线段树? A:树状数组代码更简洁且在解决前缀和问题上效率相当。
Q:1-based转换是否必要? A:不是绝对必要但能简化代码逻辑,避免处理0索引。
Q:如何处理数值很大的情况? A:可以通过离散化将大范围数值映射到小范围。
来源:竞赛学习
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

SEO网站 ( 冀ICP备2025113422号-2 )|网站地图

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表