【树状数组 求逆序对】排序

首先需要了解逆序对是什么

逆序对就是如果i > j && a[i] < a[j],这两个就算一对逆序对。其实也就是对于每个数而言,找找排在其前面有多少个比自己大的数

那么思路就来了,树状数组又一次地优化了这种“需要遍历”的情况。那不就很容易了吗?依次把序列里的数放到树状数组中的A[i]上去(实际是以C[i]形式的插入函数),注意A[i]是以数值大小从小到大排列的。先插入的说明排在序列的前面,那么后插入的就可以看看之前插入的比你大的数有多少,即i-sum(i),其实也就是看序列前面比你大的数有多少个,即找逆序对。

通过树状数组找逆序对的原理(图解,我看了就懂了):

https://blog.csdn.net/ssimple_y/article/details/53744096


了解技巧“离散化”

这个技巧很有局限性(至少以我目前的认知来说),几乎只适合在树状数组求逆序对来结合使用的。

什么时候要用这个技巧呢?根据以上说的原理,我们知道需要以“数值大小”作为A[i]标准来升序排,所以需要给C数组开元素最大可能值的内存。但万一输入的数据很大,那么C数组岂不是要开很大?内存超限!!而如果要用离散化,只需要给C数组开元素数量的内存。

建立一个结构体包含val和id, val就是输入的数,id表示输入的顺序。然后按照val从小到大排序,如果val相等,那么就按照id排序。

扫描二维码关注公众号,回复: 918619 查看本文章

如果没有逆序的话,肯定id是跟i(表示拍好后的顺序)一直一样的,如果有逆序数,那么有的i和id是不一样的。所以,利用树状数组的特性,我们可以简单的算出逆序数的个数。

如果还是不明白的话举个例子。(输入4个数)

输入:9 -1 18 5

输出 3.

输入之后对应的结构体就会变成这样

val:9 -1 18 5

id:  1  2  3  4

排好序之后就变成了

val :  -1 5 9 18

id:      2 4  1  3

2 4 1 3 的逆序数 也是3

之后再利用树状数组的特性就可以解决问题了

我觉得很神奇,直接得到“离散化”的结论把原序列中每个元素的值和下标存到一个结构体node里去,之后把node数组按元素值大小从小到大排序(注意结构体里的重载<运算符的写法 不是男左女右了我结论有误T T 反正考场上试一试即可),这样得到的结点的下标值即是离散化结果,等效于原序列的数值。把这些下标值当成原序列,按照树状数组求逆序对的原理做。



习题:排序



大致思路


这个就是逆序对的定义呀!求逆序对的模板题。

这道题如果不离散化,C开1e9内存,超限,所以离散化,C只需要开5e5内存。


AC代码

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=500001;
int c[maxn];
struct Node
{
	int v,index;
	bool operator < (const Node &b) const
	{
		return v<b.v; //从小到大排序 
	}
}node[maxn];
int n;
void add(int i)
{
	while(i<=n)
	{
		c[i]++;
		i+=i&(-i);	
	}
}
long long sum(int i)
{
	long long res=0;
	while(i>0)
	{
		res+=c[i];
		i-=i&(-i);
	}
	return res;
}

int main()
{
	cin>>n;
	int a;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		node[i].index=i;
		node[i].v=a;
	}
	sort(node+1,node+1+n);
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		add(node[i].index);  //离散化结果—— 下标等效于数值
		ans+=i-sum(node[i].index); //得到之前有多少个比你大的数(逆序对)
	}
	cout<<ans;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/80330157