逆序对 【luogu P1908】C++题解

先看题目:

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

---------------------一条正义的分割线---------------------------

根据我两年半单身练习的exp,做题第一眼先看提示,分析算法时间复杂度。

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

得出代码时间复杂度 ∈ [ O ( n ) , O ( n 2 ) ) \in [O(n),O(n^2)) [O(n),O(n2))

又双分析O(n)的时间复杂度不太可能(for循环遍历 a 1 , … , a n a_1,\dots,a_n a1,,an,每个数还要进行 > O ( 1 ) >O(1) >O(1)的操作)。

得出时间复杂度 ≈ O ( l o g n ) \approx O(logn) O(logn)

解法一:归并排序

由归并的步骤可联想到使用归并排序时判断,代码如下。

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
long long n,a[500005],cnt;
void Msort(int l,int r){
	if(l==r)return;
	int mid=(l+r)/2;
	Msort(l,mid);
	Msort(mid+1,r);
	int *b=new int[r+1],k=l,i=l,j=mid+1;
	while(i<=mid&&j<=r){
		//TODO
		if(a[i]<=a[j])b[k]=a[i],i++,k++;
		else{
			b[k]=a[j];k++;j++;
			cnt+=mid-i+1;
		}
	}
	while(i<=mid)b[k]=a[i],i++,k++;
	while(j<=r){
		b[k]=a[j],j++,k++;
	}
	for(int i=l;i<=r;i++){
		//TODO
		a[i]=b[i];
	}
	delete[]b;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	Msort(1,n);
	cout<<cnt;
	return 0;
}

代码简单,经分析,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

归并算法AC

解法二:树状数组

怎样想到树状数组捏?
考虑根据值来建树状数组 , 初始树状数组为全 0。现在按照序列从左到右将数据的值对应的位置的数加一,代表又有一个数出现。因此,在循环到第 i 项时,前 i−1 项已经加入到树状数组内了 , 树状数组内比 a i a_i ai大的都会与 a i a_i ai 构成逆序对,因为它们一定出现的更早,所以产生的逆序对数量为 i − q u e r y ( a i ) i-query(a_i) iquery(ai)

so code:

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int n,handle[1000005];
struct node{
	int num,rank;
	bool operator<(const node&b)const{
		if(num==b.num)return rank<b.rank;
		return num<b.num;
	}
}a[1000005];
int tree[1000005]; 
int lowbit(int x){
	return x&(-x);
}
void insert(int p,int d){
	while(p<=n){
		tree[p]+=d;
		p+=lowbit(p); 
	}
}
int query(int p){
	int sum=0;
	while(p){
		sum+=tree[p];
		p-=lowbit(p);
	}
	return sum;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].num;
		a[i].rank=i;
	}sort(a+1,a+1+n);
	for(int i=1;i<=n;i++){
		handle[a[i].rank]=i;
	}
	int res=0;
	for(int i=1;i<=n;i++){
		insert(handle[i],1);
		res+=i-query(handle[i]);
	}
	cout<<res<<endl;
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值