2023年 GESP六级 小杨的握手问题的优雅解法:树状数组实战
一、问题背景与算法选择
题目要求计算n个人按照特定顺序排队时发生的握手次数,本质上是计算序列中逆序对的数量。树状数组(Fenwick Tree)因其高效的区间查询和单点更新能力(O(logn))成为解决此类问题的理想选择。
二、完整代码实现(带详细注释)
#include <iostream> #include <vector> using namespACe std; // 树状数组实现类 class FenwickTree { private: vector tree; // 存储树状数组 int size; // 数组大小 public: // 构造函数,初始化大小为n的树状数组 FenwickTree(int n) : size(n), tree(n + 1, 0) {} // 更新操作:在index位置增加delta void update(int index, int delta) { // 典型的树状数组更新方式 for(; index <= size; index += index & -index) tree[index] += delta; } // 查询操作:求前index个元素的和 int query(int index) { int res = 0; // 典型的树状数组查询方式 for(; index > 0; index -= index & -index) res += tree[index]; return res; } }; int main() { // 优化输入输出 ios::sync_with_stdio(false); cin.tie(nullptr); int N; cin >> N; vector<int> order(N); for(int i = 0; i < N; i++) { cin >> order[i]; order[i]++; // 转换为1-based索引 } // 初始化树状数组 FenwickTree ft(N); long long handshakes = 0; // 核心计算逻辑 for(int i = 0; i < N; i++) { int current = order[i]; // 查询比current小的已存在数的个数 handshakes += ft.query(current - 1); // 将当前数加入树状数组 ft.update(current, 1); } cout << handshakes << endl; return 0; }
三、算法核心解析
树状数组原理:
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:可以通过离散化将大范围数值映射到小范围。
原创内容 转载请注明出处