离散化树状数组求逆序对

离散化树状数组求逆序对

今天在学校 o j oj oj上看见一道求逆序对的题,上一次企图用冒泡排序做,结果wa了几发。学习树状数组时,猛然发现树状数组还可以求逆序对,于是打开了新的大门。
传送门:39.106.31.26/problem.php?id=3677(洛谷也有这道题P1908)

猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i &lt; a j a_i&lt;a_j ai<aj i &lt; j i&lt;j i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
I n p u t Input Input
第 一 行 , 一 个 数 n , 表 示 序 列 中 有 n 个 数 。 ( n ≤ 40000 ) 第一行,一个数n,表示序列中有n个数。(n≤40000) nnn40000)
第 二 行 n 个 数 , 表 示 给 定 的 序 列 。 第二行n个数,表示给定的序列。 n
O u t p u t Output Output
给 定 序 列 中 逆 序 对 的 数 目 。 给定序列中逆序对的数目。
S a m p l e Sample Sample I n p u t Input Input
6
5 4 2 6 3 1
S a m p l e Sample Sample O u t p u t Output Output
11

解题思路

我们针对样例来理解如何采用树状数组求逆序对。

1:

最初的条件如下:
在这里插入图片描述

2:

我们利用update(5),将第5个位置置为1。计算 [ 1 , 5 ] [1,5] [1,5]上比5小的数字,这里用到了树状数组的 g e t s u m ( x ) getsum(x) getsum(x)操作,现在用输入的下标 i − g e t s u m ( x ) i-getsum(x) igetsum(x) 就可以得到对于逆序数。即: g e t s u m ( 5 ) = 1 , r e s = i − g e t s u m ( 5 ) = 1 − 1 = 0 。 getsum(5)=1,res=i-getsum(5)=1-1=0。 getsum(5)=1,res=igetsum(5)=11=0
在这里插入图片描述

update函数:
void update(ll x){
	while(x<=n){
		ans[x]++;
		x+=lowbit(x);
	}
}
getsum函数:
ll getsum(ll x){
	ll res=0;
	while(x){
		res+=ans[x];
		x-=lowbit(x);
	}
	return res;
}
3:

跟上面情况相同,先把第4个位置置为1, g e t s u m ( 4 ) = 1 , r e s = i − g e s u m ( 4 ) = 2 − 1 = 1 getsum(4)=1,res=i-gesum(4)=2-1=1 getsum(4)=1,res=igesum(4)=21=1
在这里插入图片描述

4:

把第2个位置置为1, g e t s u m ( 2 ) = 1 , r e s = i − g e s u m ( 2 ) = 3 − 1 = 2 。 getsum(2)=1,res=i-gesum(2)=3-1=2。 getsum(2)=1,res=igesum(2)=31=2
在这里插入图片描述

5:

把第6个位置置为1, g e t s u m ( 6 ) = 4 , r e s = i − g e s u m ( 6 ) = 4 − 4 = 0 。 getsum(6)=4,res=i-gesum(6)=4-4=0。 getsum(6)=4,res=igesum(6)=44=0
在这里插入图片描述

6:

把第3个位置置为1, g e t s u m ( 3 ) = 2 , r e s = i − g e s u m ( 3 ) = 5 − 2 = 3 。 getsum(3)=2,res=i-gesum(3)=5-2=3。 getsum(3)=2,res=igesum(3)=52=3
在这里插入图片描述
6:把第1个位置置为1, g e t s u m ( 1 ) = 1 , r e s = i − g e s u m ( 1 ) = 6 − 1 = 5 。 getsum(1)=1,res=i-gesum(1)=6-1=5。 getsum(1)=1,res=igesum(1)=61=5
在这里插入图片描述

7:

把每一步的res加起来就是最后的答案了, s u m = 0 + 1 + 2 + 0 + 3 + 5 = 11 。 sum=0+1+2+0+3+5=11。 sum=0+1+2+0+3+5=11


这道题看似仿佛已经讲完了,但是仔细思考思考里面居然有一个大坑。这个坑是我们树状数组造成的。当我们输入的值 a i = 999999999 a_i=999999999 ai=999999999这样庞大的值是,数组 a n s [ x ] ans[x] ans[x]是肯定存不下的。取一个极端情况,有两个值: a 1 = 1 , a 2 = 1 0 10 a_1=1,a_2=10^{10} a1=1,a2=1010,按照上面的要求我们需要使 a n s [ 1 0 10 ] = 1 ans[10^{10}]=1 ans[1010]=1,那么这个数组大部分内存都浪费了,并且也不支持你开这么大,在这样的情况下,我们将数组离散化。

推荐一篇写离散化的blog:
https://blog.csdn.net/qq_41754350/article/details/81199993

简单的说就是我们用数值下标替代了它的值进行操作。
在这里插入图片描述
将上述的 A [ i ] A[i] A[i]进行排序,用位置 b [ i ] b[i] b[i]代替自己的原来的值,起到离散化作用。
在这里插入图片描述


没想到吧,这里居然还有个坑,离散化的数据一定要去重,不然在求逆序数时会得到错误的解。为了能够偷懒,给大家推荐一个排序:stable_sort()用法和sort()差不多,好处是能去重嘛,要是不懂大家可以百度一下。

最后上代码:

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll maxn=5*1e5+10;
ll b[maxn],n,ans[maxn];
struct code{
	ll val,wz;
}a[maxn];
bool cmp(code a,code b)
{
	return a.val<b.val;
}
ll lowbit(ll x){
	return x&(-x); 
}
void update(ll x){
	while(x<=n){
		ans[x]++;
		x+=lowbit(x);
	}
}
ll sum(ll x){
	ll res=0;
	while(x){
		res+=ans[x];
		x-=lowbit(x);
	}
	return res;
}
int main(){
	ll res=0,i;
	scanf("%lld",&n);
	for(i=1;i<=n;i++){
		scanf("%lld",&a[i].val);
		a[i].wz=i;
	}
	stable_sort(a+1,a+1+n,cmp);//稳定排序 
	for(i=1;i<=n;i++){
		b[i]=a[i].wz;//离散化
	}
	for(i=1;i<=n;i++){
		update(b[i]);
		res+=i-sum(b[i]);
	}
	printf("%lld\n",res);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值