树状数组求逆序对及离散化

树状数组求逆序对及离散化
逆序对指的是一个序列中有两个数ai和aj,i<j&&ai>aj,即它们下标与数值的增减不一致,那么对于这个问题:求一个序列中逆序对的个数,该如何解决呢?
我最初接触到的方法是归并排序,是个很不错的方法,但是对于向我一样的蒟蒻……还是有理解难度,而今天讲的树状数组解法,至少……理解难度降低了不少。
树状数组求逆序的思想事实上和树状数组关系不大,以下图为例(自己画的,丑:):
如上图,第一次将第一个数1对应的a[1]++,这时还看不出来,再将4对应的a[4]++,同理a[2]++……即将n对应的a[n]++,这样做,就可以将一个无序的序列变得有序,同时a数组也表示了对应下标的数是否出现/处理过,而且当只有i个元素变换之时,剩余元素不会对接下来的操作造成影响,现在给出计算到2时的图片:
那么,对于最新处理的2或任意一个n(设下标为i)来说,前i个数中,比它小及其本身的数的个数,就是前i项的前缀和,因为排在原序列中i之后的数尚未处理,而已处理的a中比他小的数必然在它前面,且对应a[]值为1,那么,已处理的i个数中,比这个数n大的数的个数,也就是这一个数的逆序对数,就是i-getsum(n),而前缀和的求值与a数组的修改、维护,就可以交给树状数组了:
int lowbit(int a)
{
    return a&(-a);
}
void add(int p,int c)
{
    while(p<=n)
    { C[p]+=c;p+=lowbit(p);}
}
int getsum(int p)
{
    LL ans=0;
    while(p)
    { ans+=C[p];p-=lowbit(p)   }
      return ans;
}
函数并没有变化,只是主函数中的调用变成了这样:
add(a[i], 1);
ans += i - getsum(a[i]);
枚举一下i就可以了
但是,这个问题还有大坑!!我们的插入,是基于修改n对应的a[n]的,但是,如果n达到了2^31级?你开的下这么大的数组吗?
这个时候,就是离散化出场的时候了,离散化作为一种常见的优化方式,其实原理很简单,用一个结构体将数的值和下标联系在一起,再按数值排一次序,将i赋给loc[a[i].pos]//pos为下标//,这样,就将数据范围压缩在了序列长度以内,极大的压缩了空间:
struct Node
{
	int val,pos;
}a[100000]
bool cmp(Node a,Node b)
{
    if(a.val==b.val)return a.pos<=b.pos;
    else return a.val<b.val;
}
for(int i=1;i<=n;i++){
        scanf("%d",&a[i].val);
        a[i].pos=i;
    } 
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
        tre[a[i].pos]=i;
比如100 1 5 20 2 离散化为5 1 3 4 2,极大的节省了空间。
树状数组求逆序对,说到底,就是对求和函数的特殊运用,光会求逆序对,作用不大,关键是要明白怎样根据题目要求,去处理序列,求和到底可以干什么?这些都需要经验积累,推荐几道题:洛谷P1908 P1521 poj2299
poj 3067 火柴排队 奶牛集会,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值