[刷题之旅no35]逆序对

这篇博客介绍了如何使用归并排序和树状数组解决逆序对问题。首先,解释了归并排序的思想和如何在归并过程中计算逆序对,然后指出了在实现过程中的错误并提供了修正后的代码。接着,转向树状数组的解决方案,详细阐述了树状数组的原理,以及如何通过离散化和线段树的前缀和来计算逆序对。最终给出了完整的离散化和树状数组计算逆序对的代码实现。
摘要由CSDN通过智能技术生成

1.归并排序思想。
归并排序,就是把两个有序数对进行比较合并,变成一个有序数对
如何得到两个有序数对呢?就是把两个有序数对分别再化成两个有序数对,然后再合并
所以用到了递归的思想
我们取其中一个状况来看,左边是从小到大的一个数列,右边是从小到大的一个数列,而当前数列中逆序对的个数如何来求呢?
我们就要再归并过程中进行比较,如果左边一个元素大于右边一个元素,那么这个左边的元素的后面的所有元素就都大于右边这个元素
如果左边这个元素不大于右边这个元素,那么左边这个元素也不会大于右边这个元素的所有元素,所以正常入队即可。
如果当左边的元素大于右边元素的时候我们就更新答案,也就是左边元素大于右边元素的个数即可。
即:pa(左边当前元素下标),左边元素的个数就是mid-pa+1;OK
简单在归并排序中加入我需要的
ans=ans+mid-pa+1;之后,我并没有得到我需要的答案。
出现的问题是:没有记录最后一次进行组合的逆序对
原来是归并函数出现了问题
pb应该等于mid+1;前面诊断错误了
是我整个归并排序错了,归并排序完成!
归并排序:

#include<stdio.h>
int arr[500005]={0},tmp[500005]={0},pa,pb,pt,n;
long long ans=0;
void scan()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&arr[i]);
	}
}
void sort(int first,int end)
{
	int mid=(first+end)/2;
	if(first<end)
	{
		sort(first,mid);
		sort(mid+1,end);
	}
	pa=first,pb=mid+1,pt=0;
	while(pa<=mid&&pb<=end)
	{ 
		pt++;
		if(arr[pa]>arr[pb])//大于右边数组的值,更新答案 
		{
			ans=ans+mid-pa+1;
			tmp[pt]=arr[pb];
			pb++;
		}
		else//小于右边的数组,没必要更新答案了 
		{
			tmp[pt]=arr[pa];
			pa++;
		}
	}
	while(pa<=mid)
	{
		pt++;
		tmp[pt]=arr[pa];
		pa++;
	}
	while(pb<=end)
	{
		pt++;
		tmp[pt]=arr[pb];
		pb++;
	}
	pt=1;
	for(int i=first;i<=end;i++,pt++)
	{
		arr[i]=tmp[pt];
	}
	return ;
}
void print()
{
	for(int i=1;i<=n;i++)
	{
		printf("%d ",arr[i]);
	}
}
int main()
{
	scan();
	sort(1,n);
	printf("%lld",ans);
}

我们再来看看树状数组是如何处理这个问题的。
数据结构:
1.tree[]线段树数组
2.a[]储存数组值数组,这个是一个结构体数组,内容为数组值和下标
3.b[]离散化处理数组、
4.cnt为数组b赋值
思路:
1.读取值
2.a排序
3.离散化处理更新b
4.构建树,同时在构建的过程中更新答案。
理解;
b数组的作用
1.下标表示对应元素出现的先后,下标越小的越先出现
2.值就是离散化后的值,根据这个值来在树状数组中寻址
树状数组原理;
树状数组,
真正的用处就是来求解一段范围内的和
修改操作不能算用处,只是根据其特性来进行一些修改操作罢了
正常数组中我们求逆序对是怎么求的呢?
从第一个元素开始遍历,对于每个元素遍历其前面的数,遇到比他大的就加一,代表多了一个逆序对
而你看这遍历每个元素,并对于每个元素遍历其前面的数的这个操作是不是就是一种求区间值,还是前缀和
那么我们就利用这个特性,来构造线段树求前缀和,只需要让线段树的值等于其对应区间内大于我的个数就好了

说是这样说,到底该怎么做呢?
那么我们的离散化就派上了用场
离散化数据使得我们的b的值很小,虽然小,但是依旧可以表达大小关系
那么我们就让b[i]当作线段树的下标,然后把相应位置的树状数组的值加一,
也就是所有包含b[i]大小的区间都加1
这样吧。我们先不讨论算法,先把b数组全都加到这个树状数组中看看是一个什么样的结果
想一想,当我们求解tree[b[i]]开始的前缀和,是不是就是小于等于b[i]的元素总数量?
但是问题来了,我们如何判断这些小于等于b[i]的元素,到底谁比b[i]先出现,谁后出现呢?
这就是我们程序的重要部分
我们提到,b[i]这个数组下标的含义就是b[i]对应的值在原数组中的出场序号
i越小,越先出场,所以对于b我们就只需要求比它先出场,还比他大的元素就可以了对吧?
而当我们在构造这个树状数组的时候,我们就是从i=1开始构造
那么当i=k的时候,我问问你,现在求tree[b[k]]的前缀和是什么?
答案就是比b[k]出现早,而且小于等于b[k]的元素总个数啊!
而到了b[k],一共出现的元素个数是k个,
那么我问问你?
比b[k]出现早,还比他大的元素个数是几个?
当然是!
k-前缀和函数(b[k])个!
有没有思路了!
哈哈
只需要在构造树状数组的过程中,不断更细ans值就可以啦
现在需要梳理一下离散化的思路
给定已经排序完成的数组,将其离散化
1.从1到n进行遍历
2.第一的时候cnt不加,而且需要判断
通过!
附代码:

#include<stdio.h>
int b[500005]={0},tree[500005]={0};
typedef struct
{
	int val,pos;
}Arr; 
Arr a[500005];
int n;
void scan()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].val);
		a[i].pos=i;
	}
}
void sort()
{
	for(int gap=n/2;gap>0;gap=gap/2)
	{
		for(int i=gap+1;i<=n;i++)
		{
			for(int j=i;j-gap>0&&a[j-gap].val>a[j].val;j=j-gap)
			{
				Arr tmp;
				tmp=a[j-gap];
				a[j-gap]=a[j];
				a[j]=tmp;
			}
		}
	}
}
void discrete()
{
	int cnt=1;
	b[a[1].pos]=1;
	for(int i=2;i<=n;i++)//现在从1开始遍历排序之后的a数组
	{
		if(a[i].val!=a[i-1].val)
		{
			cnt++;
		}
		b[a[i].pos]=cnt;
	}
}
int lowbit(int x)
{
	return x&-x;
}
void update(int pos,int val)
{
	for(int i=pos;i<=n;i=i+lowbit(i))
	{
		tree[i]=tree[i]+val;
	}
}
int sumup(int pos)
{
	int sum=0;
	for(int i=pos;i>0;i=i-lowbit(i))
	{
		sum=sum+tree[i];
	}
	return sum;
}
void cal()
{
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		update(b[i],1);
		ans=ans+i-sumup(b[i]);
	}
	printf("%lld",ans);
}
int main()
{
	scan();
	sort();
	discrete();
	cal();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值