树状数组求逆序对/ 兼板子 (有无重复数字都可)

归并排序和树状数组都可以用nlogn的算法做到求出逆序对.但这里着重讲树状数组的原理与求法.
树状数组最常用的方面就是用来求逆序对, 普通方法需要n^2的复杂度, 而树状数组只需要用nlogn的复杂度, 所以是很好的优化, 关键在于内部函数lowbit的应用.
这是树状数组的结构图 : lowbit函数就是进行哪些实现之间的转化的, 因为这些数之间在二进制中存在着某种联系, 而lowbit函数便可把这些联系体现出来.!!!

image.png

最经典的栗子:

  • C1 = A1
  • C2 = A1 + A2
  • C3 = A3
  • C4 = A1 + A2 + A3 + A4
  • C5 = A5
  • C6 = A5 + A6
  • C7 = A7
  • C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

举个例子,有几个数可以通过lowbit产生0100呢?0001–lowbit–>0010–>lowbit–>0011–>lowbit–>0100,有三个数可以通过lowbit产生0100,加上它本身,总共维护了2^2个数的和。比如5(0101)没有数可以通过lowbit产生,所以维护它本身一个。6(0110)可以有5(0101)lowbit产生,所以维护两个

总之记住树状数组实现的是nlogn的算法, 和他具体实现的是什么功能就行了.
当数据的范围较小时,比如maxn=100000,那么我们可以开一个数组c[maxn],来记录前面数据的出现情况,初始化为0;当数据a出现时,就令c[a]=1。这样的话,欲求某个数a的逆序数,只需要算出在当前状态下c[a+1,maxn]中有多少个1,因为这些位置的数在a之前出现且比a大。但是若每添加一个数据a时,就得从a+1到 maxn搜一遍,复杂度太高了。树状数组却能很好的解决这个问题,可以把数一个个插入到树状数组中, 每插入一个数, 统计比他小的数的个数,对应的逆序为 i - getsum( c[i] ),其中 i 为当前已经插入的数的个数, getsum( c[i] )为比 c[i] 小的数的个数,i- getsum( c[i] ) 即比c[i] 大的个数, 即逆序的个数。最后需要把所有逆序数求和,就是在插入的过程中边插入边求和.

举个例子:有5个数,分别为5 3 4 2 1,当读入数据a=5时,c为:0,0,0,0,1;d为:0,0,0,0,1;当读入数据a=3时,c为:0,0,1,0,1;d为:0,0 , 1,1,1;当读入数据a=4时,c为:0,0,1,1,1;d为:0,0,1,2,1;
此思想的关键在于,读入数据的最大值为maxn,由于maxn较小,所以可以用数组来记录状态。当maxn较大时,直接开数组显然是不行了,这是的解决办法就是离散化。所谓离散化,就是将连续问题的解用一组离散要素来表征而近似求解的方法,这个定义太抽象了,还是举个例子吧。
   假如现在有一些数:1234 98756 123456 99999 56782,由于1234是第一小的数,所以num[1]=1;依此,有num[5]=2,num[2]=3,num[4]=4,num[3]=5;这样转化后并不影响原来数据的相对大小关系,何乐而不为呢!!!
还有一点值得注意,当有数据0出现时,由于0&0=0,无法更新,此时我们可以采取加一个数的方法将所有的数据都变成大于0的.

单纯求逆序对的, 正着插求后缀和, 反着插求前缀和. (其中x的前缀和是sum[x-1])!!! (同理 x 的后缀和是sum[x+1]). 如果不是严格的逆序也就是包括等于号的话, 那么求前缀的时候 x的前缀和是sum[x] 也就是等于号啦~~~

具体看代码 : 树状数组+去重离散化
1: 倒着做求前缀和
2: 顺着做求后缀和

int c[maxn], n;
void add(int x) {
   
    for ( ; x <= n ; x += x & -x) {
   
        c[x]++;
    }
}
int getsum(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值