求排列的逆序数(分治)
Posted 程章培
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了求排列的逆序数(分治)相关的知识,希望对你有一定的参考价值。
题目描述:考虑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;
认真想一想,这个代码和归并排序的代码的区别,就是在归并排序的同时进行答案的计算,把两边按照从小到大来排序。
以上是关于求排列的逆序数(分治)的主要内容,如果未能解决你的问题,请参考以下文章