求排列的逆序数(分治)

题目描述:考虑1,2,…,n (n <= 100000)的排列i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij > ik, 那么就称(ij,ik)是这个排列的一个逆序。
一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个 逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。

现给定1,2,…,n的一个排列,求它的逆序数。

 

其实笨办法很简单就是涉及一个双重循环。遍历一个数之后再往后遍历找有没有比这个数小的数,有的话就输出相应的数。双重循环很明显复杂度是O(n^2)。

现在使用分治的思维,就是左边来找逆序数,右边再找逆序数,任何再找右边一个数左边一个数的逆序数(要求O(n)实现)。找左右两边都有的逆序数关键:

求排列的逆序数(分治)

 

  一开始让i指向开头的元素,让j指向右半边开头的元素。将i与j比较,如果右半边的数比左半边大的话就把j往后移动知道j移到5停止。这样后面的所有数就与10构成逆序数。任何i++就好了j也直接往后走。

/*
  归并排序是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为 
*  若干个子序列,每个子序列是有序的,然后再把有序的子序列合并为整体有序序列 
*  归并排序是分治算法的一个典型的应用,而且是稳定的一种排序,这题利用归并排序 
*  的过程中,计算每个小区间的逆序数,进而得到大区间的逆序数。那么,问题就解决了。
归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并
排序中的合并过程中计算逆序数.
*/
 
#include <iostream>
#include <string.h>
#include <stdio.h>
 
using namespace std;
#define N  1000002
 
long long  a[N],tmp[N];
long long ans;
//归并排序的合并部分 
void Merge(int l,int m,int r)
{
    int i = l;
    int j = m + 1;
    int k = l;
    while(i <= m && j <= r)
    {
        if(a[i] > a[j])
        {
            tmp[k++] = a[j++];
            ans += m - i + 1;
        }
        else
        {
            tmp[k++] = a[i++];
        }
    }
    while(i <= m) tmp[k++] = a[i++];
    while(j <= r) tmp[k++] = a[j++];
    for(int i=l;i<=r;i++)
        a[i] = tmp[i];
}
//l左端点,r右端点 
//归并排序 
void Merge_sort(int l,int r)
{
    if(l < r)
    {
        int m = (l + r) >> 1;
        Merge_sort(l,m);//将前半部分排序 
        Merge_sort(m+1,r);//将后半部分排序 
        Merge(l,m,r);//合并前后两个部分 
    }
}
 
int main()
{
    int n,T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        ans = 0;
        //0左端点,n-1右端点 
        Merge_sort(0,n-1);
        printf("%lld\n",ans);
    }
    return 0;
}

 

 认真想一想,这个代码和归并排序的代码的区别,就是在归并排序的同时进行答案的计算,把两边按照从小到大来排序。

 

上一篇:洛谷比赛/补题集合


下一篇:如何在Java中实现高效的去重优先队列