P1908逆序对(树状数组解法)

逆序对问题有两种标准解法,即归并排序和树状数组解法,这里我主要详细说一下树状数组我的理解和解法

归并排序解法如下(不详细解释)

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
LL n,arr[1000005],cnt=0;
void Merge(LL arr[], LL l, LL m, LL h) {
	
	LL i = l, j = m + 1, k = 0;
	LL a[h - l + 100];
	while (i <= m && j <= h) {
		if (arr[i] <=arr[j]) {
			a[k++] = arr[i++];
		} else {    //去掉这里就是纯的归并排序
			a[k++] = arr[j++];
			cnt+=m-i+1;
		}
	}
	while (i <= m) 
		a[k++] = arr[i++];
	while (j <= h) 
		a[k++] = arr[j++];
	for (i = 0, j = l; j <= h; i++, j++) {
		arr[j] = a[i];
	}
}
void MergesortDC(LL arr[], LL l, LL h) {
	LL m;
	if (l < h) {
		m = (l + h) / 2;
		MergesortDC(arr, l, m);
		MergesortDC(arr, m + 1, h);
		Merge(arr, l, m, h);
	}
}
int main(){
	scanf("%lld",&n);
	for(int i=0;i<n;i++)
		scanf("%lld",&arr[i]);
	MergesortDC(arr,0,n-1);
	printf("%lld",cnt);
	return 0;
}

下面用树状数组求解: 

这里我们是使用树状数组进行处理的,先看一下树状数组的维护代码(表示a位置上的数加b):

void update(int a,int b){
	for(int i=a;i<=50005;i+=lowbit(i))
		tree[i]+=b;
}

然后就是求前缀和的代码:

int sum(int a){
	int he=0;
	for(int i=a;i>0;i-=lowbit(i))
		he+=tree[i];
	return he;
}

 

用树状数组解法需要用到一个技巧:把数字看成数组的下标来使用。例如:序列如果为{5,4,1,2,3}的话,对应a[5],a[4],a[1],a[2],a[3]。每处理一个数字,树状数组的该下标所对应的元素值加1,统计前缀和,就是逆序对的数目。有两种处理方法:1:倒序,2:正序n

1)倒序:倒序的意思就是从后面往前面处理,每处理一个数n,先把该下标a[n]对应值加1,然后逆序对ans的值就变为ans+=sum(n-1),也就是逆序对变为逆序对加上这个数的前一个所对应的前缀和。(这时的前缀和就是当前数n为逆序对大数的逆序对的值,仔细想想就能明白,此时出现在这个下标前面的数是肯定比这个数要小的,因为下标比n小,而又是进行倒序处理,也就是此时这个下标前面出现的数是这个序列里面排在这个数后面的,那么就符合逆序对的定义了,所以此时的前缀和当然就是这个数为逆序对大数的逆序对的值了

2)正序:正序的意思就是从前面往后面处理,跟倒序差不了多少,每处理一个数n,先把该下标a[n]对应值加1,这时候我们直接求这个数n的前缀和,至于为什么,是为了我们更好的继续逆序对,不理解的请接着看,由于是正序处理的这个,那么当前已经处理的这个时候得到的前缀和是正序对加上数值相同的对,跟上面一样,此时出现在这个下标前面的数是肯定是这个数要小的,因为下标比n小,但因为是正序处理,也就是此时这个下标前面出现的数是这个序列里面排在这个数前面的,也就是i<j且a[i]<a[j],那么就符合正序对的定义了,又因为此时我们求得前缀和是sum(n),包含了n,所以存在相等的对,又一个数列里面所有的对只有正序对,相等对和逆序对,那么逆序对自然而然就出来了,求全部对可以直接采用等差数列的前n项和:1+2+3+......+n,自己跟自己也算相等的一对

不过上面的处理方法还是存在一个问题,那就是如果数太大了,就很容易造成浪费空间,这里我们可以采用离散化的方法,用相对大小来表示,例如,一个数列为{1,23232424,122,3545},那么我们可以用相对大小的数列来表示,表示成{1,4,2,3},离散化计算相对大小需要用到排序

接下来请看这题的代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define lowbit(a) ((a)&-(a))
int n;
struct node{
	int num,val;
}a[500010];
int ran[500010],tree[500010];
int cmp(node i,node j){//这里需要注意:如果两数相等那么就让先出现的更小
	if(i.num==j.num)
		return i.val<j.val;
	return i.num<j.num;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		{
	    scanf("%d",&a[i].num);
		a[i].val=i;//每个数进行编号
	}
	sort(a+1,a+n+1,cmp);//排序
	for(int i=1;i<=n;i++)
		ran[a[i].val]=i;//离散化过程,从最小到最大一一赋值相对大小
	long long ans=0;
	for(int i=1;i<=n;i++)//正序处理
	{
		update(ran[i],1);
		ans+=i-sum(ran[i]);//这里是用了个技巧因为i是从1-n,这里我们直接这样子用,省去步骤
	}
//	for(int i=n;i>0;i--){ //倒序处理
//		update(ran[i],1);
//		ans+=sum(ran[i]-1);
//	}
	printf("%lld",ans);
	return 0;
}

update函数和求前缀和sum函数前面已经给出,不懂的可以去看下树状数组的基础部分

这是本人第一篇,如果有错误之处欢迎指正,如果还是不理解的也可以在评论区讲出,看到一定恢复,这也是一个相互进步的过程

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值