洛谷 P1908 逆序对(堆排)

题目大意:

有一个数列,问ai>aj同时i<j的元素对 在这个数列中有多少对

解题思路:

首先,暴力思路n^2,直接TLE,所以考虑更快的算法。

这里我们假设堆排从小到大。

在这里我们要使用堆排,为什么使用堆排呢,因为在堆排中,我们假设左右序列都已经排好序了,所以我们再找逆序对的时候,其实减少了重复工作,比如:现在左边序列是 5,6;右边序列是4,7。那么我们的流程是这样的:

i=j=0  //i和j分别是左右序列的指针

5>4

ans+=1 //ans 是我们的结果

j+=1  //右边序列指针加一

5<7

i+=1  //访问左边序列的下一个元素的意思

关键的地方是这时候,我们的ans+=j 

为什么可以这样呢?因为j代表i前面的最大的元素大于右边序列的j个元素,所以每次i+=1,我们认为这个访问到的左边序列的新元素是大于j个元素的,因为左边序列已经进行了排序!ai已经是当前遍历的左序列最大的元素了

题外话:

堆排里面把n^2能够压缩为n log n,很重要的思想是,我们必须假设左右两段序列(堆排每次都会把序列分成两部分,除非这段只剩一个元素)已经排好序,进行指针操作插入到新序列,这时候我们的指针操作不再是n^2而是 n。比如,在堆排中,我们已经知道一部分排好序了,那么当我们遍历完左序列或者右序列插入一部分序列之后,剩下的左序列或者右序列元素可以直接插入到新序列里面。

#include <bits/stdc++.h>
#define int long long 
using namespace std;
const int MAXN=5e5+10;
int arr[MAXN];
int ans=0;
int ltemp[MAXN];
int rtemp[MAXN];
void merge(int l,int r){
	int m=l+(r-l)/2;
	int count=0;
	for(int i=l;i<=m;i++){
		ltemp[count++]=arr[i];}
	count=0;
	
	for(int i=m+1;i<=r;i++){
		rtemp[count++]=arr[i];}
	int lcount=m-l+1;
	int rcount=r-(m+1)+1;
	int i=0;int j=0;int cur=l;
	while(i<lcount&&j<rcount){
		if(ltemp[i]<=rtemp[j]){
			arr[cur++]=ltemp[i];
			i++;
			if(i!=lcount)
			ans+=j;
		}else{
			ans+=1;
			arr[cur++]=rtemp[j];
			j++;
		}
	}
	if(j==rcount)ans+=(lcount-(i+1))*rcount;
	for(;i<lcount;i++)arr[cur++]=ltemp[i];
	for(;j<rcount;j++)arr[cur++]=rtemp[j];
	assert(cur==r+1);
}
void merge_sort(int l,int r){	
	if(l==r)return;
	int m=l+(r-l)/2;
	
	merge_sort(l,m);
	merge_sort(m+1,r);
	merge(l,r);
}
int32_t main(){
	// interesting
	int n;;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>arr[i];
	}
	merge_sort(0,n-1);
	cout<<ans<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值