首先是归并排序的板子
int n,a[100000],b[100000];
void msort(int l,int r)
{
if(l==r) return;
int mid=l+r>>1;
msort(l,mid);
msort(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j]) b[k++]=a[i++];
else b[k++]=a[j++];
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(i=l;i<=r;i++) a[i]=b[i];
}
求逆序对只需要添加一个计数器,当合并的时候把右边区间的数字放到了左边,说明这个时候左边区间剩余的数字是和这个数字逆序的。count+=mid-1+1;
这里需要注意的一点是,当出现了重复的数字,我们的处理是a[i]<=a[j]的先放,也就是说如果出现了两个一样的数字,我们会先放左面的,再放右边的。也就不会增加多余的逆序对。
归并排序求逆序对的板子:
long long int count;
int n,a[100000],b[100000];
void msort(int l,int r)
{
if(l==r) return;
int mid=l+r>>1;
msort(l,mid);
msort(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j]) b[k++]=a[i++];
else b[k++]=a[j++],count+=mid-1+1;
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(i=l;i<=r;i++) a[i]=b[i];
}
学长讲到归并排序求逆序对,改变了a本身的顺序,没有保序性,所以不太常用。
而且求逆序数的时候,其实是在归并排序的各个子数组有序的前提下才能在O(1)的条件下实现。
如果遇到某些题求a[i],a[j]的大小关系,而且可以证明子区间是单调的,那么可以直接套用模板。
动态求逆序对,寻找保序可以用树状数组。
P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
洛谷P1908:离散化+树状数组,天才的思路,即通过树状数组动态地查询前缀和,用前缀和表示出小于该数字且位置大于该数字的数量。
具体的流程是这样:
创建一个结构体,存数字的位置pos和值val。先按val的大小降序排序,如果val相同,再按pos的大小降序排序(对相同数字的处理)。
排序之后,先放大的数字(这样每次查询前缀和区间的时候,前缀和的结果就是大于放入数字的个数),即change(a[i].pos,1)。存入的是位置,因为大小通过排序已经确定了,但是位置必须要在该数字之前才能满足逆序的条件。
每放入一个数字就要计算大于它且位置在它之前的个数,sum+=query(a[i].pos-1)。
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
struct node{
int val,pos;
}a[510000];
int s[510000];
int n;
int lowbit(int x){
return x&-x;
}
void change(int x,int k)
{
while(x<=n) s[x]+=k,x+=lowbit(x);
}
bool cmp(node x,node y){
if(x.val==y.val){
return x.pos>y.pos;
}else return x.val>y.val;
}
int query(int x){//查询区间和
int t=0;
while(x) t+=s[x],x-=lowbit(x);
return t;
}
signed main(){
while(cin>>n&&n){
for(int i=1;i<=n;i++){
cin>>a[i].val;a[i].pos=i;
}
sort(a+1,a+n+1,cmp);
int sum=0;
for(int i=1;i<=n;i++){
change(a[i].pos,1);
sum+=query(a[i].pos-1);
}
cout<<sum<<endl;
}
return 0;
}