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;
}
posted @ 2021-04-10 20:46  归游  阅读(81)  评论(0编辑  收藏  举报