P1908 逆序对(归并排序)

题目描述:

题目传送门


解题思路:

对于这个问题,我们显然能通过冒泡或选择排序来累计交换次数达到所求目的。但很显然, Θ ( n 2 ) \Theta(n^2) Θ(n2) 的排序无法承担 5 ∗ 1 0 5 5*10^5 5105 的数据,因此我们需要用到 Θ ( n   l o g n ) \Theta(n~log_n) Θ(n logn) 的归并排序。

归并排序:

首先我们考虑要排序一段 ( l , r ) (l,r) (l,r) 的区间,归并排序需要考虑分裂和合并两个步骤,我们先看看分裂。
在这里插入图片描述

如图,对于一个 ( l , r ) (l,r) (l,r) 的区间,归并排序会将这个区间分成两个长度相等的左右区间 ( l , m i d ) (l,mid) (l,mid) ( m i d + 1 , r ) (mid+1,r) (mid+1,r),这样原问题就分解成了等价的两个子问题,然后子问题再进一步分裂,分成更小的问题,知道分成每个区间只有一个元素 a i a_i ai,为止。此时,当区间长度为 1 1 1,我们发现区间本身已经是有序的了,因此我们要把这种子区间的有序带到父区间中,也就是合并。

将左右各自有序的区间合并成跟大的区间,这样的话父区间也将有序,然后继续合并,等最后的根区间合并完了,那么 ( l , r ) (l,r) (l,r) 区间就变得有序了。

合并步骤 Θ ( n ) \Theta(n) Θ(n),分裂是 Θ ( l o g n ) \Theta(log_n) Θ(logn),归并排序的复杂度就是 Θ ( n   l o g n ) \Theta(n~log_n) Θ(n logn)

这里有必要讲讲 Θ ( n ) \Theta(n) Θ(n) 合并的过程,如图:
在这里插入图片描述
我们需要一个一开始 指向区间 A A A 头部的指针 i i i ,还需要一个指向区间 B B B 头部的指针 j j j,开始考虑比较 a i a_i ai 以及 b j b_j bj,若 a i > b j a_i>b_j ai>bj 则将 b j b_j bj 插入 i i i 前,也就是 i − 1 i-1 i1 的位置,并将 j j j 指针往后移动一位;若 a i < b j a_i<b_j ai<bj 则将 i i i 指针往后移动一位, 以此往复,区间将成功有序合并。

在程序中我们需要额外的一个数组作为空间存放这个合并后的区间,最后还得把这个数组重新放入区间 A A A B B B

以下是完整的归并排序代码:

int t[100010]={0}; //额外空间
void msort(int l,int r)
{
	if(l-r==0) return ;
	int mid;
	mid=l+(r-l)/2;
	msort(l,mid);  //左区间排序
	msort(mid+1,r);  //右区间排序
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	  {
	  	if(a[i]>a[j])
	  	  {
	  	  	t[k]=a[j];
	  	  	j++;
	  	  	k++;
		  }
		else
		  {
		  	t[k]=a[i];
		  	i++;
		  	k++;
		  }
	  }
	while(i<=mid)  //若某一段区间插入完毕了,剩下的直接放进去就行了
	  {
	  	t[k]=a[i];
	  	i++;
	  	k++;
	  }
	while(j<=r)
	  {
	  	t[k]=a[j];
	  	j++;
	  	k++;
	  }
	for(i=l;i<=r;i++)
	  a[i]=t[i];
	return ;
}

那么对于题目中的逆序对个数问题,我们可以找到一些合并过程中 B B B 区间的数小于 A A A 区间的数的数对 ,简单推推就能发现,一个包含 a i a_i ai 的这样的数对就能带来 m i d − i + 1 mid-i+1 midi+1 个逆序对。原因也很简单,由于 A A A 区间中的数是有小到大有序排列的,可知,若 a i > b j a_i>b_j ai>bj i i i以后的所有 a a a 也都必定大于 b j b_j bj ,即都是逆序对,个数为 m i d − i + 1 mid-i+1 midi+1


CODE:

#include <bits/stdc++.h>
using namespace std;
int n,a[500010]={0},t[500010]={0};
long long ans=0;
void msort(int l,int r)
{
	if(l-r==0) return ;
	int mid;
	mid=l+(r-l)/2;
	msort(l,mid);
	msort(mid+1,r);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	  {
	  	if(a[i]>a[j])
	  	  {
	  	  	t[k]=a[j];
	  	  	j++;
	  	  	k++;
	  	  	ans+=(mid-i+1);  //统计逆序对
		  }
		else
		  {
		  	t[k]=a[i];
		  	i++;
		  	k++;
		  }
	  }
	while(i<=mid)
	  {
	  	t[k]=a[i];
	  	i++;
	  	k++;
	  }
	while(j<=r)
	  {
	  	t[k]=a[j];
	  	j++;
	  	k++;
	  }
	for(i=l;i<=r;i++)
	  a[i]=t[i];
	return ;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	  cin>>a[i];
	msort(1,n);
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值