Fenwick 树进行快速统计

Fenwick 树(二叉索引树)是一种高效的数据结构,主要用于解决前缀和查询单点更新 问题,时间复杂度均为O(log n),适用于需要频繁更新元素并查询前缀和的场景(如本题统计满足条件的对数)。

核心功能

Fenwick 树支持两种基本操作:

  1. 单点更新:给数组中某个位置的元素加上一个值。
  2. 前缀和查询 :计算数组中前k个元素的总和。

原理与结构

Fenwick 树的核心是利用二进制的特性 (低位的1)来划分区间,实现高效的更新和查询。

结构特点:
  • 树是1-based 索引(从 1 开始计数,方便二进制操作)。
  • 每个节点i负责管理一段区间,区间范围由i的二进制中最低位的 1 决定(例如i=6的二进制是110,最低位的 1 对应的值是2,所以节点 6 管理[6-2+1, 6] = [5,6])。

基本操作详解

1. 单点更新(update(i, delta)

给位置i的元素增加delta步骤 :从i开始,不断加上i的二进制中最低位的 1(即i += i & -i),直到超过树的大小。作用 :更新所有包含位置i的区间节点。

复制代码
void update(int idx, int delta) {
    for (; idx <= size; idx += idx & -idx) {
        tree[idx] += delta;
    }
}
2. 前缀和查询(query(i)

计算前i个元素的总和。步骤 :从i开始,不断减去i的二进制中最低位的 1(即i -= i & -i),直到i=0,累加经过的节点值。作用 :将前i个元素拆分成若干个被节点管理的区间,求和后得到前缀和。

复制代码
int query(int idx) {
    int res = 0;
    for (; idx > 0; idx -= idx & -idx) {
        res += tree[idx];
    }
    return res;
}

在本题中的应用

在一个序列2n,1-n每个数都会出现两次,计算有多少对数的数,满足x1 < y1 < x2 < y2。

本题需要统计满足x1 < y1 < x2 < y2的数对数量:

  1. 先将非相邻数按x1排序(确保处理顺序满足x1' < x1)。
  2. 对每个数的x2进行坐标压缩 (将分散的x2值映射到连续的小范围,节省空间)。
  3. 用 Fenwick 树记录已处理数的x2,对于当前数的x2,通过query(x2-1)快速统计出之前所有x2' < 当前x2的数量(即满足x1' < x1 < x2' < x2的对数)。

#include <bits/stdc++.h>

using namespace std;

// Fenwick树( Binary Indexed Tree )用于高效统计

struct Fenwick {

int size;

vector<int> tree;

// 初始化大小为n的Fenwick树

Fenwick(int n) : size(n), tree(n + 2, 0) {} // 1-based索引

// 更新操作:在idx位置加delta

void update(int idx, int delta) {

for (; idx <= size; idx += idx & -idx) {

tree[idx] += delta;

}

}

// 查询操作:求[1, idx]的前缀和

int query(int idx) {

int res = 0;

for (; idx > 0; idx -= idx & -idx) {

res += tree[idx];

}

return res;

}

};

int main() {

ios::sync_with_stdio(false);

cin.tie(nullptr);

int T;

cin >> T;

while (T--) {

int N;

cin >> N;

int len = 2 * N;

vector<int> A(len);

for (int i = 0; i < len; ++i) {

cin >> A[i];

}

// 记录每对夫妇的两个位置

vector<pair<int, int>> pos(N + 1); // pos[x] = (第一个出现位置, 第二个出现位置)

vector<int> first_occurrence(N + 1, -1);

for (int i = 0; i < len; ++i) {

int x = A[i];

if (first_occurrence[x] == -1) {

first_occurrence[x] = i; // 记录第一次出现位置

} else {

pos[x] = {first_occurrence[x], i}; // 记录第二次出现位置

}

}

// 筛选出原本不相邻的数

vector<pair<int, int>> S;

for (int x = 1; x <= N; ++x) {

auto [x1, x2] = pos[x];

if (x2 - x1 != 1) { // 相邻的条件是位置差为1

S.emplace_back(x1, x2);

}

}

// 按第一个位置x1排序(确保我们按顺序处理)

sort(S.begin(), S.end());

// 坐标压缩:将x2值映射到较小的范围,便于Fenwick树处理

vector<int> x2s;

for (auto [x1, x2] : S) {

x2s.push_back(x2);

}

sort(x2s.begin(), x2s.end());

x2s.erase(unique(x2s.begin(), x2s.end()), x2s.end()); // 去重

int m = x2s.size();

// 用Fenwick树统计符合条件的对数

Fenwick fenwick(m);

long long ans = 0;

for (auto [x1, x2] : S) {

// 找到x2在压缩后的排名(1-based)

int r = lower_bound(x2s.begin(), x2s.end(), x2) - x2s.begin() + 1;

// 查询当前有多少个x2' < x2(即满足交叉条件的数量)

ans += fenwick.query(r - 1);

// 将当前x2加入Fenwick树

fenwick.update(r, 1);

}

cout << ans << '\n';

}

return 0;

}

相关推荐
渡我白衣12 小时前
无中生有——无监督学习的原理、算法与结构发现
人工智能·深度学习·神经网络·学习·算法·机器学习·语音识别
小龙报12 小时前
【数据结构与算法】单链表的综合运用:1.合并两个有序链表 2.分割链表 3.环形链表的约瑟夫问题
c语言·开发语言·数据结构·c++·算法·leetcode·链表
蓝海星梦12 小时前
GRPO 算法演进:2025 年 RL4LLM 领域 40+ 项改进工作全景解析
论文阅读·人工智能·深度学习·算法·自然语言处理·强化学习
拼好饭和她皆失12 小时前
图论:最小生成树,二分图详细模板及讲解
c++·算法·图论
傻小胖12 小时前
19.ETH-挖矿算法-北大肖臻老师客堂笔记
笔记·算法·区块链
郝学胜-神的一滴12 小时前
线性判别分析(LDA)原理详解与实战应用
人工智能·python·程序人生·算法·机器学习·数据挖掘·sklearn
ScilogyHunter12 小时前
CW方程的向量形式与解析形式
算法·矩阵·控制
蓝海星梦12 小时前
GRPO 算法演进——奖励设计篇
论文阅读·人工智能·深度学习·算法·自然语言处理·强化学习
sin_hielo12 小时前
leetcode 3013
数据结构·算法·leetcode
格林威12 小时前
Baumer相机系统延迟测量与补偿:保障实时控制同步性的 5 个核心方法,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·视觉检测·工业相机