最近做题遇到几次逆序数了,今天总结一下,以后遇到了再也不怕了。
首先说明一下什么是逆序数,下面是百度的定义:
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
如2431中,21,43,41,31是逆序,逆序数是4。
①如何求逆序数
方法如下:
考察每一位,判断从这一位往后有多少小于该位的,结果累加,得到最后结果。
例如,2,4,3,1 先判断2,之后有1是小于2的,结果+1,再判断4,之后3,1都小于4,结果+2,判断3时,结果再+1,最后得到结果4.
至于为什么,我也不知道,反正我感觉挺好理解的。
②如何用算法实现
当然,对于上述简单的过程,我们可以用两个for循环直接暴力,但是,这种方法一般都太low了,复杂度太高。
高效的方法有 : 归并排序、线段树、树状数组。
个人建议使用线段树求解,因为线段树在很多地方都有应用。
对于归并排序和树状数组,我就不解释了,因为归并排序一搞就忘记了而且用处不大,想了解的这里给出一篇博客
https://blog.csdn.net/acm_jl/article/details/51147010
例题是 HDU 1394,然后做完这个可以再写一下POJ 2299
HDU 1394的解析如下
https://www.cnblogs.com/qlky/p/5693747.html
然后我写的是POJ 2299
下面开始解析,当然,大家一定要有一定线段树的基础
算法的思想还是最基础的思想,考察每个点,判断它以后有多少个小于它的,然后累加。
与之前不同的地方在于,求小于某个数的个数时用到了线段树,所以非常快
这里的线段树表示某个范围内的值个数query 1--3,就表示值在1--3的个数
然后对于1--n个数,我们每个数都进行两步操作
查询1--a[i],单点更新a[i],值+1
例如对于 2 4 3 1 序列,我们从右向左判断,首先查询1-1,没有,值为0,然后1点值更新
再查询1--3,值为1,(实质表示的意思是比3小的有1个),然后更新3,
再查询4,更新4
查询2,更新2,得到最后的结果
下面是POJ 2299 的代码,要注意的是要离散化一下,因为值比较大
#include <iostream>
#include <cstdio>
#include<algorithm>
#include <cstring>
#include <stack>
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = 1 ; j <= r ; j++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;
struct spe
{
int val,pos;
};
const int maxn = 5e5+5;
spe p[maxn];
int n;
int pos[maxn];
int tree[maxn<<2];
int L,R;
bool cmp( spe a,spe b )
{