逆序对: 对于给定的一段正整数序列,逆序对就是序列中 ai > aj 且 i < j 的有序对。
(逆序数的求法至少有三种:枚举法、分治法、树状数组。分治法和树状数组时间复杂度都是O( Nlog2N )。。下面先说说分治法。。。)
归并排序解法(分治法):
归并排序(merge sort)是建立在归并操作上的一种有效的排序算法。 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
时间复杂度为O(nlogn),空间复杂度为 O(n),归并排序比较占用内存,但却效率高且是稳定的算法
//在某个时候,左区间: 5 6 7 下标为 i
// 右区间: 1 2 9 下标为 j
// (数组若从下标为0开始,则mid=2,若从下标为1开始,则mid=3)
//这个时候我们进行合并:
//step 1:由于 5>1,所以产生了逆序对,这里,我们发现,左区间所有还没有被合并的数都
比 1 大,所以1与左区间所有元素共产生了 3 个逆序对( **即mid-i+1对** ),统计答案并合并 1
//step 2:由于 5>2,由上产生了3对逆序对,统计答案并合并 2
//step 3:由于 5<9, 没有逆序对产生,右区间下标 j++
//step 4:由于 6<9, 没有逆序对产生,右区间下标 j++
//step 5:由于 7<9, 没有逆序对产生,右区间下标 j++
//step 6:由于右区间已经结束,正常执行合并左区间剩余,结束
代码实现如下:
/*去掉计算逆序对个数ans的语句,就是标准的归并排序(递归法)*/
#include<stdio.h>
int ans=0;///统计逆序对个数
void merge_array(int a[], int first, int mid, int last, int temp[])//将二个有序数列a[first→mid]和a[mid→last]合并。
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] <= a[j]){
temp[k++] = a[i++];
}
else{
temp[k++] = a[j++];
ans+=mid-i+1;///计算逆序对个数
}
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
void merge_sort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
merge_sort(a, first, mid, temp); //左边有序
merge_sort(a, mid + 1, last, temp); //右边有序
merge_array(a, first, mid, last, temp); //再将二个有序数列合并
}
}
int main()
{
int a[50002]={},temp[50002]={},n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
merge_sort(a,0,n-1,temp);///数组下标从0开始,所以注意这里传递给函数的last是n-1
for(int i=0;i<n;i++){
printf("%d ",a[i]); ///输出已排序后的数组
}
puts("");
printf("%d\n",ans);///输出逆序对个数
return 0;
}
或
#include<cstdio>
#include<iostream>
using namespace std;
int n,a[500010],c[500010];
long long ans;
void msort(int b,int e)//归并排序
{
if(b==e)
return;
int mid=(b+e)/2,i=b,j=mid+1,k=b;
msort(b,mid),msort(mid+1,e);
while(i<=mid&&j<=e)
if(a[i]<=a[j])
c[k++]=a[i++];
else
c[k++]=a[j++],ans+=mid-i+1;//统计答案
while(i<=mid)
c[k++]=a[i++];
while(j<=e)
c[k++]=a[j++];
for(int l=b;l<=e;l++)
a[l]=c[l];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
msort(1,n);
printf("%lld",ans);
return 0;
}
非递归法请看:二路归并排序算法(递归&非递归)
OJ标程:
// 分治法求逆序数
#include<stdio.h>
#include<string.h>
#define inf 51000
int a[inf],b[inf];
long long int find(int low,int top)
{
if(low>=top)
return 0;
int mid=(low+top)/2,i=low,j=mid+1,k=low;
long long int sum;
sum=find(low,mid)+find(mid+1,top);
while(i<=mid&&j<=top)
{
if(a[i]>a[j])
{
sum+=mid-i+1;
b[k++]=a[j++];
}
else
b[k++]=a[i++];
}
while(i<=mid)
b[k++]=a[i++];
while(j<=top)
b[k++]=a[j++];
memcpy(a+low,b+low,sizeof(a[0])*(top-low+1));
return sum;
}
int main()
{
int i,n,T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%lld\n",find(1,n));
}
return 0;
}
树状数组+离散化 解法
树状数组的做法以及离散化等内容见洛谷题解:P1908 逆序对 题解
用树状数组求解的思路:
先以桶排的思想,开一个大小为这些数最大值的树状数组,并全部置0。从头到尾读入这些数,每读入一个数就在相应位置+1并更新树状数组 ,查看它前面比它小的已出现过的个数sum(查看这一步其实就是对树状数组[1, i]进行区间求和操作),然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了。把所有的加起来就是总的逆序对数。
有时候数会很大,利用桶排思想建树状数组空间不够,比如共有5e5个数,但每个数的的范围是小于等于1e9,这时候就可以对数据离散化,因为求解逆序对我们只关心数的相对大小,而不关心数的具体值。
代码实现如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
int n;
int qq[maxn]; //离散化后的数组
struct node {
int x, id;
bool operator< (const node &b)const {
if (x == b.x) return id < b.id;
else return x < b.x;
}
}a[maxn];
struct BIT {
int c[maxn];
int lowbit(int x) {return x & (-x);}
void add(int i, int val) {
while (i <= n) {
c[i] += val;
i += lowbit(i);
}
}
ll query(int i) {
ll ret = 0;
while (i > 0) {
ret += c[i];
i -= lowbit(i);
}
return ret;
}
}tr;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i].x), a[i].id = i;
/*离散化*/
sort(a+1, a+n+1);
for (int i = 1; i <= n; i++)
qq[a[i].id] = i;
/*树状数组求逆序对*/
ll ans = 0;
for (int i = 1; i <= n; i++) {
tr.add(qq[i], 1);
ans += i - tr.query(qq[i]);
}
printf("%lld\n", ans);
return 0;
}
能用树状数组当然也能用线段树了。以下为线段树做法:
// 线段树
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define MAX 51000
#define MID(a,b) (a+b)>>1
#define R(a) (a<<1|1)
#define L(a) a<<1
typedef struct {
int num,left,right;
}Node;
typedef struct {
int num,y;
}Nodes;
Nodes ans[MAX];
Node Tree[MAX<<2];
int n;
bool cmp(Nodes a,Nodes b)
{
return a.num>b.num?0:1;
}
bool cmp2(Nodes a,Nodes b)
{
return a.y>b.y?0:1;
}
void Build(int t,int l,int r)
{
int mid;
Tree[t].left=l,Tree[t].right=r;
if(Tree[t].left==Tree[t].right)
{
Tree[t].num=0;
return ;
}
mid=MID(Tree[t].left,Tree[t].right);
Build(L(t),l,mid);
Build(R(t),mid+1,r);
}
void Insert(int t,int l,int r,int x)
{
int mid;
if(Tree[t].left==l&&Tree[t].right==r)
{
Tree[t].num+=x;
return ;
}
mid=MID(Tree[t].left,Tree[t].right);
if(l>mid)
{
Insert(R(t),l,r,x);
}
else if(r<=mid)
{
Insert(L(t),l,r,x);
}
else
{
Insert(L(t),l,mid,x);
Insert(R(t),mid+1,r,x);
}
Tree[t].num=Tree[L(t)].num+Tree[R(t)].num;
}
int Query(int t,int l,int r)
{
int mid;
if(Tree[t].left==l&&Tree[t].right==r)
return Tree[t].num;
mid=MID(Tree[t].left,Tree[t].right);
if(l>mid)
{
return Query(R(t),l,r);
}
else if(r<=mid)
{
return Query(L(t),l,r);
}
else
{
return Query(L(t),l,mid)+Query(R(t),mid+1,r);
}
}
int main()
{
int a,n,i,t;
scanf("%d",&t);
long long int k;
while(t--)
{
scanf("%d",&n);
memset(Tree,0,sizeof(Tree));
Build(1,1,n);
for(i=1;i<=n;i++)
{
scanf("%d",&ans[i].num);
ans[i].y=i;
}
sort(ans+1,ans+1+n,cmp);
for(i=1;i<=n;i++)
ans[i].num=i;
sort(ans+1,ans+1+n,cmp2);
for(i=1,k=0;i<=n;i++)
{
a=ans[i].num;
Insert(1,a,a,1);
k=k+(i-Query(1,1,a));
}
printf("%lld\n",k);
}
return 0;
}