前言
求逆序对数之前我们在讲树状数组的时候讲过,那个时候我们采用的方法是先对数据进行离散化处理,缩减数据范围,再将数据依次放入数组中,在这个过程中通过计算区间和统计对应逆序对个数;
此时我们将采用另一个思路——线性分治。
分治的思想我们学习排序的时候接触过一些,比如归并排序,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
一、分治思路
计数类题目最重要的原则是不重不漏。
在一段线性区间中取一个平衡点,一个逆序对要么位于点的同一侧,要么分别位于两侧;
如果我们对一侧按照相同的策略进行处理,也就是进行分治,那么最后可以得到同一种情况:
逆序对位于对应平衡点的两侧
这个分治的思路就需要跟我们之前的归并排序联系起来了。
归并排序同样是使用分治思想,在平衡点左右两侧的区段均已被排好序,
那么此时我们有两个条件了:
1.逆序对位于左右两侧;
2.左右两侧已经排好序了;
此时想要求逆序对就比较简单了
遇到逆序对的情况,就可以得出一类逆序对的数量,直接加上就可以了
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
tmp[k++]=a[i++];
}
else {
ans+=mid-i+1;
tmp[k++]=a[j++];
}
}
二、代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
char buf[1<<20],*P1=buf,*P2=buf;
#define gc() (P1==P2&&(P2=(P1=buf)+fread(buf,1,1<<20,stdin),P1==P2)?EOF:*P1++)
#define TT template<class T>inline
TT bool read(T &x){
x=0;char c=gc();bool f=0;
while(c<48||c>57){if(c==EOF)return 0;f^=(c=='-'),c=gc();}
while(47<c&&c<58)x=(x<<3)+(x<<1)+(c^48),c=gc();
if(f)x=-x;return 1;
}
const ll N=5e5+5;
ll n,a[N],b[N],cnt;
void merge(ll l,ll r){
ll mid=l+r>>1;
if(l>=r)return;
merge(l,mid);
merge(mid+1,r);
ll i=l,j=mid+1;
fir(k,l,r){
if(j>r||i<=mid&&a[i]<=a[j])b[k]=a[i++];
else b[k]=a[j++],cnt+=mid-i+1;
}
fir(k,l,r)a[k]=b[k];
}
int main(){
while(read(n)&&n){
cnt=0;
fir(i,1,n)read(a[i]);
merge(1,n);
cout<<cnt<<endl;
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500005;
int a[N];
int tmp[N];
int n;
ll mort(int l,int r){
int mid = (l+r)/2;
if(l>=r)return 0;
ll ans=mort(l,mid)+mort(mid+1,r);
int i=l,k=l,j=mid+1;
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
tmp[k++]=a[i++];
}
else {
ans+=mid-i+1;
tmp[k++]=a[j++];
}
}
while(i<=mid)tmp[k++]=a[i++];
while(j<=r)tmp[k++]=a[j++];
for(int i=l;i<=r;i++)a[i]=tmp[i];
return ans;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
cout<<mort(0,n-1)<<endl;
}