求逆序对的三种方法

逆序对

在数组A[x]中,若存在(i < j) && (A[i] > A[j]),则称(A[i],A[j])为数组A[x]的一个逆序对


暴力O(N^2)求法

    for(i = 1; i <= n; i ++)
        for(j = i; j <= n; j ++)
            if(A[i] > A[j])cnt ++;

归并排序求法

由上面的表述可知,逆序对同样可以表示为:在数组A[x]中,若存在(i > j) && (A[i] < A[j]),则称(A[i],A[j])为数组A[x]的一个逆序对
那么,设cnt[i]为A[i]前比它大的数字个数,逆序对数则 = cnt[1] + cnt[2] + cnt[3] + ... + cnt[n];
一只总逆序对数为每个数字前比它大的数字个数之和,那么我们只需要在归并排序中统计这个数字个数即可

#include<iostream>
...
void merge(int L, int R, int Mid){
    int i = L;int j = Mid + 1;int k = L;
    while(i <= Mid && j <= R){
        if(s1[i] <= s1[j])s2[k ++] = s1[i ++];
        else{
            cnt += Mid - i + 1;
            //对于i到Mid这Mid - i + 1个数字(假设它为j),每一个a[j]都与a[i]构成了一个逆序对
            s2[k ++] = s1[j ++]; 
        }
    }
    while(i <= Mid)s2[k ++] = s1[i ++];
    //对于每个j都已讨论完,每个j钱比他大的数都已计入cnt,所以此时不用更新cnt
    while(j <= R)s2[k ++] = s1[j ++];
    for(i = L; i <= R; i ++)s1[i] = s2[i]; 
}
void mergesort(int L, int R){
    if(L < R){
        int Mid = (L + R) / 2;
        mergesort(L, Mid),mergesort(Mid + 1, R);
        merge(L, R, Mid);
    }
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++)scanf("%d", &s1[i]);
	mergesort(1, n);
	for(int i = 1; i <= n; i ++)printf("%d", s1[i]);
}

树状数组求逆序对

虚拟一个数组A[i],A[i] == 1表示出现了i,A[i] == 0表示没有出现i
利用树状数组,getsum(i)表示i~i中出现的数字个数
边输入边进行操作,对于当前的数字x,它所贡献的逆序对书即为在它之前出现的比它大的数字的总数,即为当前x~n的sum值->getsum(n)-getsum(x);之后再将A[i] ++,更新C[i](modify)

#include<iostream>
#define lowbit(x) x & -x
...
void modify(int x, int d){
	for(int i = x; i <= n; i += lowbit(i))c[i] += d;
}
int getsum(int x){
	int sum = 0;
	for(int i = x; i >= 1; i -= lowbit(i))sum += c[i];
	return sum;
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++){
		scanf("%d", &x);
		ans += getsum(n) - getsum(x);
		modify(x, 1)''
	}
}
posted @ 2019-07-07 16:08  羽错光阴  阅读(7861)  评论(3编辑  收藏  举报