一、问题背景与算法选择题目要求计算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)
空间复杂度:
优化方向:
五、常见问题解答Q:为什么选择树状数组而不是线段树? A:树状数组代码更简洁且在解决前缀和问题上效率相当。 Q:1-based转换是否必要? A:不是绝对必要但能简化代码逻辑,避免处理0索引。 Q:如何处理数值很大的情况? A:可以通过离散化将大范围数值映射到小范围。 |