P1908 逆序对
题目描述
P1908 逆序对
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 ai>aj
且 i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
思路
-
暴力求解
如果暴力求解,那么思路很简单
从后往前找,先从末尾开始
拿当前这个数与之后的数比较
如果比当前数小就直接ans++,
最后枚举完了,就是最终答案
时间复杂度 O(n*n) TLE -
分治
如果求一个区间内的逆序对,是不是可以转化为求两个区间内的逆序对
(那么就会有同学问,你本来一个区间内的逆序对,可能会拆开啊,计算时不会被计算到啊?)
这个问题很简单嘛,你直接再拿两个区间的数再比较就彳亍
实现:归并排序
这时又有一个问题,归并排序,是会排序的啊,会打乱原来顺序啊
其实并不影响,因为比较的是两个区间内的各个数的大小,而左区间的下标一定比右区间的下标小,即使区间内排了序也不影响
遗留问题:既然利用归并排序,如果每次像暴力一样比较,那也会使时间复杂度变为O(n*n),所以如果计算是关键
其实归并时,就在比较两个数的大小,如果左区间的数一直小于等于右区间的数,直到碰到右区间的一个数小于左区间的数,是不是之后的所有数,即[i,mid]的数(i为左区间位置 都与当前这个数互为逆序对,所以此时答案就可以直接加上前面剩下左区间的个数 -
树状数组
可能有同学就会问了,树状数组不是用来计算区间和吗,这个逆序对和区间合有毛的关系?
这里就很巧妙了
回到暴力的想法,有两步
第一步,从后往前枚举
第二步,拿当前这个数与前面的所有数一一比较
第一步,不能再优化了(你每个都不枚举一下,答案就会有错)
第二步,其实是在寻找比 枚举到当前数 小的数,这里就可以树状数组来查询,每次被枚举后的数需要更行sum[1,a[i] ]
时间复杂度O(n*logn)
注意:如果a[i]的范围过大 需要离散化
当然其实也可以从前往后枚举,读者可以自行思考
代码
归并排序
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n;
const int maxn=5*1e5+100;
int m[maxn];
int mn[maxn];
long long ans=0;
void msort(int a,int b){//因为左区间的下标始终大于右区间下标,所以就算排序了也不影响比较大小
if(a==b) return ;
int mid=a+b>>1;
int i=a,k=a,j=mid+1;
msort(a,mid);msort(j,b);
while(i<=mid && j<=b) {
if(m[i]<=m[j]) mn[k++]=m[i++];
else mn[k++]=m[j++],ans+=mid-i+1;//如果左区间的某一个数大于右区间的一个数
} // 又因为当前i-mid都是有序的,所以i之后的数也大于当前j这个数,所以对于当前j还有mid-i+1个逆序对
while(i<=mid) mn[k++]=m[i++];
while(j<=b) mn[k++]=m[j++];
for(int ii=a;ii<=b;++ii) m[ii]=mn[ii];
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>m[i];
msort(1,n);
cout<<ans<<endl;
return 0;
}
树状数组
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
ll n;
const ll maxn=5e5+10;
ll c[maxn],p[maxn];
struct node{
ll val,pl;
}e[maxn];
bool cmp(node a,node b){
if(a.val==b.val) return a.pl<b.pl;
return a.val<b.val;
}
ll lowbit(ll x){
return x&-x;
}
ll query(ll x){
ll ans=0;
while(x){
ans+=c[x];
x-=lowbit(x);
}return ans;
}
void add(ll x){
while(x<=n){
c[x]++;
x+=lowbit(x);
}return ;
}
int main(){
cin>>n;
memset(c,0,sizeof(c));
for(int i=1;i<=n;++i){
cin>>e[i].val;
e[i].pl=i;
}
sort(e+1,e+1+n,cmp);
for(int i=1;i<=n;++i)
p[e[i].pl]=i;
ll ans=0;
for(int i=n;i;--i){
ans+=query(p[i]-1);
add(p[i]);
}
cout<<ans<<endl;
return 0;
}