求逆序对的思路总结如下:
- 对数据进行离散化处理,设离散化后的数组为a[n].
- 设置数组s[n], s[i]表示数字i在a[n]出现的次数.
- 核心步骤:
ans = 0;
s[1~n] = 0; //初始化s[n]
for i = 1, 2, …, n // 从1遍历到n
s[a[i]] += 1; //更新a[i]出现的次数
cnt = SUM(s[1~a[i]]); //cnt表示当我们遍历到第i个数时,共有多少个数<=a[i]
ans += i - cnt; //当前共有i个数, <=a[i]的又有cnt个,那么>a[i]的自然就有i - cnt个,也就是产生了i - cnt对逆序对。
不难发现,一般情况下,我们实现核心步骤需要两层循环:外层为for循环,遍历a[n];内层为SUM(s[1~a[i]]),求有多少个<=a[i]的数。因此该算法的时间复杂度O(n2).
不过,可以观察到,for循环中的s[a[i]] += 1本质上是单点更新的问题,而内层循环SUM(s[1~a[i]])本质上是一个区间求和问题,因此我们可以用树状数组来对算法优化。
理解树状数组的思想的话,应该不难推出相应的单点更新和区间求和的函数:
void update(int x, int n) // 单点更新
{
for(int i = a[x]; i <= n; i += i & -i) // 注意起点是a[x]
s[i] += 1;
}
int sum(int x) //区间求和
{
int ret = 0;
for(int i = a[x]; i >= 1; i -= i & -i) // 注意起点是a[x]
ret += s[i];
return ret;
}
树状数组求逆序对裸题:
https://www.luogu.com.cn/problem/P1908
如果对求逆序对的思想不太理解的话,可以参考:
https://blog.youkuaiyun.com/SSimpLe_Y/article/details/53744096
下面的内容就搬自以上博客,协助理解算法:
注:以下出现的函数add(x)表示令s[a[x]] + 1,函数read(x)表示SUM(s[1~a[x]]).
第一次插入的时候把5这个位置上加上1,read(x)值就是1,当前已经插入了一个数,所以他前面比他大的数的个数就等于 i - read(x) = 1 - 1 = 0,所以总数 sum += 0
第二次插入的时候,read(x)的值同样是1,但是 i - read(x) = 2 - 1 = 1,所以sum += 1
第三次的时候,read(x)的值是2,i - read(x) = 3 - 2 = 1,所以sum += 1
第四次,read(x)的值是1,i - read(x) = 4 - 1 = 3,所以sum += 3
第五次,read(x)的值是1,i - read(x) = 5 - 1 = 4,所以sum += 4
这样整个过程就结束了,所有的逆序对就求出来了。