[CP] 学习记录——树状数组求逆序对个数

NOTE1: 本文并不包含树状数组的原理及实现。
NOTE2: 主要参考 树状数组 - OI Wiki 写成。

逆序对

对于给定数组 a,记下标为 i 的元素为 ai,定义逆序对 (i,j) 为满足:

ai>aj, i<j

的数对。

使用暴力方法求逆序对个数

根据上述定义,可以很快写出暴力方法:

int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (a[i] > a[j])
++cnt;
}
}

时间复杂度 O(n2)n 较大时就会超时。

使用树状数组求逆序对个数

更快的方法是使用归并排序或树状数组,这里介绍后者。

权值数组

本方法的关键在于权值数组(如果之前学过桶排序应该很好理解)。对于数组 a,定义权值数组 b,使得 bj 等于 ja 中出现的次数。比如对于 a=[3,2,5,1,4,2],可以构造权值数组 b=[0,1,2,1,1,1]。可见,权值数组掩盖了原数组中元素之间的大小关系,这与逆序对定义中蕴含的大小关系是相悖的。

然而,如果我们一个个地向权值数组中添加元素呢?

回想先前的暴力做法,对于原数组的每个元素 ai,我们遍历 i<j<n 的数组部分,统计所有满足 ai>aj 的数对个数……

注意到,逆序对定义中的两个条件并没有先后之分,所以我们反过来思考,可以找到所有满足 ai>aj 的数对,统计 i<j<n 的个数。

如果用权值数组来实现,因为权值数组的下标严格递增,故 “所有满足 ai>aj 的数对个数” 就等于 k=0ai1bk,前提是这些位于权值数组中的元素的下标都大于 i

那么,如何保证这个前提?只需要从原数组的最后一个元素开始,一个个地将其添加到权值数组中,直到第一个元素,同时在每次添加元素时将 k=0ai1bk 累加到最终答案里面。

因为修改权值数组会影响到 k=0ai1bk 的查询,为了更高效地完成上述操作,使用树状数组来实现权值数组的功能,因此就有 “权值树状数组” 的叫法。

离散化

当输入数组 a 的元素值域较宽时,因为权值数组的下标对应的是 a 的元素值,因此可能会有较多的空间浪费,特别地,当元素值非常大时(如 1013),则根本无法开辟这样的权值数组。

这一问题可以用离散化来解决。因为我们当前关注的是元素的相对大小关系,而不关心元素的值究竟是多少,所以可以计算出每个元素的相对索引。以数组 a=[10000000,1,100,10] 为例,可以构造出如下的数组 a=[4,1,3,2],表示 “将原数组排序后,每个元素所处的位置”(为了方便,索引一般从 1 开始)。这样一来,a 的每个元素都可以通过 a 对应到权值数组的某个元素上了。

例题

F. Greetings

posted @   ZXPrism  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示