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

贾蔷
• 阅读 11

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. 树状数组原理
    • 利用二进制索引高效维护前缀和
    • update和query操作的时间复杂度均为O(logn)
    • 空间复杂度O(n)
  2. 1-based转换
    • 将输入值+1转换为1-based索引
    • 避免处理0索引带来的边界问题
  3. 逆序对计算
    • 按顺序处理每个人时
    • 查询已处理人中编号比当前小的数量
    • 累加得到总握手次数

四、复杂度分析与优化

  1. 时间复杂度
    • 预处理:O(n)
    • n次查询和更新:O(nlogn)
    • 总复杂度:O(nlogn)
  2. 空间复杂度
    • 树状数组:O(n)
    • 输入存储:O(n)
  3. 优化方向
    • 离散化处理可减少空间使用
    • 并行计算可优化大规模数据

五、常见问题解答

Q:为什么选择树状数组而不是线段树? A:树状数组代码更简洁且在解决前缀和问题上效率相当。

Q:1-based转换是否必要? A:不是绝对必要但能简化代码逻辑,避免处理0索引。

Q:如何处理数值很大的情况? A:可以通过离散化将大范围数值映射到小范围。

来源:竞赛学习

点赞
收藏
评论区
推荐文章
贾蔷 贾蔷
1个月前
2013 蓝桥杯 省赛B组 翻硬币(洛谷P8597题) 从暴力BFS到贪心算法的优化之路
一、问题背景与理解洛谷P8597是一道经典的翻硬币问题,题目描述如下:给定两个由''和'o'组成的字符串s1和s2,分别表示初始状态和目标状态。每次操作可以选择任意位置开始翻转连续的k个硬币(''变'o','o'变'')。要求计算出从初始状态变为目标状态所
贾蔷 贾蔷
1个月前
2025年GESP七级等价消除(洛谷P11965)代码解析与优化策略
一、题目解读2025年GESP七级考试中的“等价消除(洛谷P11965)”问题要求统计给定字符串中满足等价条件的子串数量。所谓“等价子串”,是指子串中所有字符出现的次数均相同。题目需要高效算法解决,考验对字符串处理和状态压缩的掌握。二、解题思路采用位运算
贾蔷 贾蔷
3星期前
2023年GESP六级小杨握手问题(洛谷B3874):Fenwick树求解逆序对的代码解析
一、题目解读“小杨的握手问题”源自2023年GESP六级考试(对应洛谷题目B3874)。题目描述为:给定一个长度为N的排列,每次将当前数与之前未访问过的数握手,求总共握手次数。本质上是求排列中逆序对的个数,即统计每个数右侧比它小的元素数量。需设计高效算法在
贾蔷 贾蔷
3星期前
2023年GESP六级题解:洛谷P10108闯关游戏动态规划解法详解
一、题目解读本文针对2023年GESP六级题目“闯关游戏”(洛谷P10108)进行详细解析。题目要求玩家通过不同关卡路径选择,计算从起点到终点的最大得分。关卡间存在跳跃规则,需结合动态规划思想设计高效算法,最终输出最优得分。二、解题思路采用动态规划(Dyn
贾蔷 贾蔷
3星期前
洛谷P1168题终极解析:双堆法高效计算动态中位数 | 数据结构实战教程
一、问题理解与算法思路题目要求我们动态维护一个序列,并在每次读取奇数个数字时输出当前序列的中位数。这道题考察了两个核心算法点:堆数据结构的应用和中位数的高效计算。我们采用双堆法(一个大根堆和一个小根堆)来高效解决这个问题。‌解题关键步骤‌:使用大根堆存储较
深度学习 深度学习
2星期前
动态规划进阶:牛客4802题带附件背包问题详解 | 组合优化技巧
一、问题背景与算法思路牛客4802题是一个典型的带附件的背包问题变种,要求在主件和附件存在依赖关系的情况下,选择物品组合使总价值最大化。本文通过动态规划方法,将问题转化为分组背包问题,通过预处理所有可能的组合方式来实现高效求解。二、完整代码实现(带详细注释
贾蔷 贾蔷
1星期前
牛客3750题滑动窗口最大值解析:双端队列优化解法与代码详解
一、题目解读要求在一个给定的整数中,计算固定大小为k的内元素的最大值。例如,当窗口滑动时,需要实时输出每个窗口中的最大值序列。该问题考察对的理解,以及如何高效维护窗口内的元素关系。二、解题思路采用(deque)维护的巧妙解法。核心思想是:中仅存储数组下标,
深度学习 深度学习
1星期前
洛谷P2190题解:铁路售票系统车厢计算(差分数组+前缀和优化)
一、题目解读P2190题要求解决铁路售票系统中的车厢数量计算问题。题目给定n个车站和m条订票申请,每条申请包含区间实现区间修改的。通过差分记录每个站点的乘客变化(如进入/离开人数),再计算前缀和得到各站点的实时乘客数,从而找出最大值。特别处理环形区间(即
深度学习 深度学习
5天前
NOIP 2008火柴棒等式题解(C++代码实现) 动态规划与枚举算法详解
一、题目解读问题(,)要求使用给定数量的火柴棒,构造形如ABC的等式,其中A、B、C均为整数,且火柴棒总数恰好等于输入值。需统计符合条件的等式数量。题目核心在于将数字拆解与火柴棒消耗建模为数学问题,寻找高效解法。二、解题思路采用火柴棒计数策略:1.关系
深度学习 深度学习
20小时前
2021年CSP-S廊桥分配(洛谷P7913):贪心算法与优先队列实战
一、问题背景分析2021年的廊桥分配问题要求分配有限廊桥资源,最大化服务国内和国际航班数量。题目核心是处理两类航班的起降时间冲突,通过动态调度实现资源高效利用。二、核心设计1.数据结构选择//优先队列存储可用廊桥编号(按编号排序)priorityqueue