7622:求排列的逆序数
传送门
归并排序的改编运用
原版归并
CPP
图解 归并排序(CPP)
JAVA
图解 归并排序(JAVA)
归并排序的思想大概就是:
将要排序的序列分成两段,对左右两段分别排序,再合并为有序序列,对于每一段等待排序的序列再次进行以上操作,直到分段到其中一段只有两个元素,再分段左右两段就是一个元素,开始进行排序,其实就是交换两个数了。从排列两个数的序列开始,合并排列好的两段序列,直到之前分的所有两段都合并好。这显然需要递归实现。想象其中一个状态,排列好了左右两段的,接下来要做的事就是合并左右两个有序段将其变为有序。
很好,把自己绕晕了👍
看别人画的归并排序的图解,一开始做的事情其实是分段,把整个序列分成左右两段,对于每一小段又分成左右两端,直到分的两段中只有一个元素,分段到此结束。开始对最小的两段合并,合并之后就可以回溯到上一阶分的段,直到这样回溯,把最开始分的那两段都合并了(你咋知道合并到了最开始的那两段呢?因为每分两段,之后都会有 merge(a,l,r,mid);合并的指令,只是要等到上面那两段都分段、合并好了。其实就是回溯完所有的递归函数,就完成了所有段的归并)
不知道说的是否确切,就像一个递归回溯的过程,
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
merge(a,l,r,mid);
只可意会不可言传😭
这道题呢,就是要走归并排序走过的路,对于可以分的每两段,把 对这两段合并的过程 变成…,其实是真合并,因为只有真合并才能进行下去,走完所有的路。合并的过程中计数 左段和右段 左大右小的 情况种数。
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n;
ll a[100009];
ll cnt=0;
void merge(ll a[],int l,int r){
ll temp[100009];//用来盛放合并好的有序序列 归并排序时间复杂度为nlogn,空间换时间
int k=0;
int mid=l+(r-l)/2;
int i=l; int j=mid+1;
while(i<=mid&&j<=r){// 82 94
// if(a[i]<=a[j]){//从小到大顺序排列
// temp[k++]=a[i++];
// }
// else{
从小到大顺序排列 ,当左边数大于右边数,左边数右侧的mid-i+1个数 都会大于这个右边数
// temp[k++]=a[j++];
// cnt+=(mid-i+1);
// }
if(a[i]<=a[j]){//从大到小顺序排列
temp[k++]=a[j++];
}
else{
//从大到小顺序排列 ,当左边数大于右边数,右边数右侧的r-j+1个数会小于这个左边数
temp[k++]=a[i++];
cnt+=(r-j+1);
}
}
while(i<=mid){
temp[k++]=a[i++];
}
while(j<=r){
temp[k++]=a[j++];
}
for(int h=0;h<r-l+1;h++){
a[h+l]=temp[h];
}
}
void mergeSort(ll a[],int l,int r){
if(l>=r)return;//递归要有终止条件噢
int mid=l+(r-l)/2;
mergeSort(a,l,mid);
mergeSort(a,mid+1,r);
merge(a,l,r);
}
int main(int argc, char** argv) {
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
mergeSort(a,0,n-1);
// 将数组分成两半,左半边的逆序数加上右半边的逆序数,
// 再加上左边取一个、右边取一个的逆序数 ,通过归并排序先为两半边排好序
cout<<cnt;
return 0;
}