学了树状数组后,突然想到了这个逆序对,我也就顺便记了下来,以防以后忘掉后不知所措。(。・・)ノ
逆序对:
一个序列a中,如果有i < j&&a[i]>a[j],那么这就是一个逆序对。
那么如何求一个序列中逆序对的个数?o(^▽^)┛
基本方法:
n2
的两重循环,暴力寻找前面比当前位置大的数。
当然,这是最蠢的方法- -|||
归并排序:
我们可以通过归并排序(从小到大),每次二分区间,在合并时,这两个区间都是单调不下降的。如果出现了左区间的一个数大于右区间的一个数,那么左区间剩下的所有数都会大于这个数,也就是左区间剩下的i-mid+1个数都会大于这个数,那么就会产生i-mid+1个逆序对,加进答案即可。
该算法高效,时间为
O(nlog2n)
。
void mesort(int l,int r)
{
if (l==r) return;
int mid=(l+r)/2;
mesort(l,mid);
mesort(mid+1,r);
int i=l,j=mid+1,t=l;
while (i<=mid&&j<=r){
if (a[i]<=a[j]) fre[t++]=a[i++];
else {
fre[t++]=a[j++];
ans+=mid-i+1;//加入逆序对。
}
}
while (i<=mid) fre[t++]=a[i++];
while (j<=r) fre[t++]=a[j++];
fo(i,l,r) a[i]=fre[i];
}
树状数组:
利用该数据结构也是个很快速求逆序对的方法,而且可以在线求(就是在序列尾新加入一个数后求逆序对)。
主要思想是:
新加入一个数x后,当前总共的数的个数为i,那么在树状数组上x的位置增加一,并且利用树状数组求出前面小于等于x的数的个数,那么新增的逆序对的个数就是(i-x)个了。
最后求个和即可。(当然注意要离散化!)时间复杂度:
O(nlog2n)
.
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int maxn=1000;
int fre[maxn],n,ans;
int lowbit(int x)
{
return x&-x;
}
void add(int k,int x)
{
while (k<=n){
fre[k]+=x;
k+=lowbit(k);
}
}
int get(int k)
{
int sum=0;
while (k>0){
sum+=fre[k];
k-=lowbit(k);
}
return sum;
}
int main()
{
scanf("%d",&n);
fo(i,1,n){
int x;
scanf("%d",&x);//注意要离散化,此次没有离散化。
add(x,1);
ans+=i-get(x);//新增的逆序对的个数。
}
printf("%d",ans);
}