1269. 【分治算法】逆序对数

题目描述

给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

输入

第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数。

输出

所有逆序对总数。

样例输入 
4
3
2
3
2
样例输出 
3
数据范围限制

对于50%的数据,n<=10^4,ai<=10^5。
对于100%的数据,n<=10^5,ai<=10^5。

归并排序

首先声明一下:这道题用冒泡排序是不能通过的,需要用归并排序

我们先来介绍一下归并排序

时间复杂度:

O(n log n)

空间复杂度:

O(n)

定义:

归并排序是建立在归并操作上的一种有效的排序算法,该算法采用的是分治法(二分法)。

算法流程:

先给个图:

设n为序列元素的个数,a[i]为其中第i个元素,msort为归并排序的函数名,b[i]为两个相邻的子序列合并完的临时有序序列

1.将一个序列进行二分,直到分解成n个元素(l == r)

如代码:

int mid = (l + r) / 2;//取中间 
if(l == r)//若l == r了,就代表这个子序列就只剩1个元素了,需要返回 
{
	return;
}
else
{
	msort(l, mid);//分成l和中间一段,中间 + 1和r一段(二分) 
	msort(mid + 1, r);
}

2.将已有序的子序列合并(二分到最后的时候,一个元素一定是有序的),得到完全有序的序列

如代码:

int i = l;//i从l开始,到mid,因为现在排序的是l ~ r的区间且要二分合并 
int j = mid + 1;//j从mid + 1开始,到r原因同上
int t = l;//数组b的下标,数组b存的是l ~ r区间排完序的值 
while(i <= mid && j <= r)//同上i,j的解释 
{
	if(a[i] > a[j])//如果前面的元素比后面大(l ~ mid中的元素 > mid + 1 ~ r中的元素)(逆序对出现!!!) 
	{ 
		ans += mid - i + 1;//由于l ~ mid和mid + 1 ~ r都是有序序列所以一旦l ~ mid中的元素 > mid + 1 ~ r中的元素而又因为第i个元素 < i + 1 ~ mid那么i + 1 ~ mid的元素都 > 第j个元素。所以+的元素个数就是i ~ mid的元素个数,及mid - i + 1(归并排序里没有这句话,求逆序对里有) 
		b[t++] = a[j];//第j个元素比i ~ mid的元素都小,那么第j个元素是目前最小的了,就放进b数组里 
		++j;//下一个元素(mid + 1 ~ r的元素小,所以加第j个元素) 
	}
	else
	{
		b[t++] = a[i];//i小,存a[i] 
		++i;//同理 
	}
}
while(i <= mid)//把剩的元素(因为较大所以在上面没选) 
{
	b[t++] = a[i];//存进去 
	++i; 
}
while(j <= r)//同理 
{
	b[t++] = a[j];
	++j;
}

将已有序的b数组赋值到a数组(原数组)里

如代码:

for(register int i = l; i <= r; ++i)//把有序序列b赋值到a里 
{
	a[i] = b[i];
}

手动模拟一下样例(以便更好的理解算法):

二分时,奇数时默认取左边n / 2 + 1,右边取n / 2了(为了方便)

二分时的图:

那么我们开始合并(看着图合并吧,并且只能同一层合并,并向上传递,我把后面两个while循环在合并中省去了)

1.第四层5和4合并:i = l = 1;mid = (l + r) / 2 = 1;r = 2;j = mid + 1 = 2;5 > 4;逆序对数量 + mid - i + 1 = 1;b数组:4 5 0 0 0 0; j + 1 > r; 退出;a数组:4 5 2 6 3 1;

2.第四层6和3合并:i = l = 4;mid = (l + r) / 2 = 4;r = 5;j = mid + 1 = 5; 6 > 3;逆序对数量 + mid - i + 1 = 2;b数组:4 5 0 3 6 0;j + 1 > r; 退出;a数组:4 5 2 3 6 1;

3.第三层4, 5和2合并: i = l = 1;mid = (l + r) / 2 = 2;r = 3;j = mid + 1 = 2;4 > 2; 逆序对数量 + mid - i + 1 = 4;b数组:2 4 5 3 6 0;j + 1 > r;退出;a数组:2 4 5 3 6 1;

4.第三层3, 6和1合并:i = l = 4;mid = (l + r) / 2 = 5;r = 6;j = mid + 1 = 6;3 > 1;逆序对数量 + mid - i + 1 = 6;b数组:2 4 5 1 3 6;j + 1 > r;退出;a数组:2 4 5 1 3 6;

5.第二层2, 4,5和1, 3,6合并:i = l = 1;mid = (l + r) / 2 = 3;r = 6;j = mid + 1 = 4;2 > 1; 逆序对数量 + mid - i + 1 = 9;b 数组:1 2 4 5 3 6;j + 1;2 < 3;b数组:1 2 4 5 3 6;i + 1;4 > 3;逆序对数量 + mid - i + 1 = 11;b数组:1 2 3 4 5 6;j + 1;4 < 6;b数组:1 2 3 4 5 6;i + 1;5 < 6;b数组:1 2 3 4 5 6;i + 1 > mid;退出; a数组:1 2 3 4 5 6(有序)

所以,输出样例就是11

code:

#include <bits/stdc++.h>
using namespace std;
int n, a[5000001], b[5000001];
long long ans;
inline void msort(int l, int r)//归并排序
{
	int mid = (l + r) / 2;//取中间 
	if(l == r)//若l == r了,就代表这个子序列就只剩1个元素了,需要返回 
	{
		return;
	}
	else
	{
		msort(l, mid);//分成l和中间一段,中间 + 1和r一段(二分) 
		msort(mid + 1, r);
	}
	int i = l;//i从l开始,到mid,因为现在排序的是l ~ r的区间且要二分合并 
	int j = mid + 1;//j从mid + 1开始,到r原因同上
	int t = l;//数组b的下标,数组b存的是l ~ r区间排完序的值 
	while(i <= mid && j <= r)//同上i,j的解释 
	{
		if(a[i] > a[j])//如果前面的元素比后面大(l ~ mid中的元素 > mid + 1 ~ r中的元素)(逆序对出现!!!) 
		{ 
			ans += mid - i + 1;//由于l ~ mid和mid + 1 ~ r都是有序序列所以一旦l ~ mid中的元素 > mid + 1 ~ r中的元素而又因为第i个元素 < i + 1 ~ mid那么i + 1 ~ mid的元素都 > 第j个元素。所以+的元素个数就是i ~ mid的元素个数,及mid - i + 1(归并排序里没有这句话,求逆序对里有) 
			b[t++] = a[j];//第j个元素比i ~ mid的元素都小,那么第j个元素是目前最小的了,就放进b数组里 
			++j;//下一个元素(mid + 1 ~ r的元素小,所以加第j个元素) 
		}
		else
		{
			b[t++] = a[i];//i小,存a[i] 
			++i;//同理 
		}
	}
	while(i <= mid)//把剩的元素(因为较大所以在上面没选) 
	{
		b[t++] = a[i];//存进去 
		++i; 
	}
	while(j <= r)//同理 
	{
		b[t++] = a[j];
		++j;
	}
	for(register int i = l; i <= r; ++i)//把有序序列b赋值到a里 
	{
		a[i] = b[i];
	}
	return;
}
int main()
{
	scanf("%d", &n);
	for(register int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i]);
	}
	msort(1, n);//一开始序列是1 ~ n 
	printf("%lld", ans);
	return 0;
}

### 使用归并排序算法计算逆序对数量 #### 方法概述 归并排序的核心思想是分治法,即将数组分为更小的部分分别处理后再合并。在合并过程中可以统计逆序对的数量。当两个子数组被分开时,左边的子数组元素大于右边的子数组元素的情况即构成逆序对[^1]。 对于暴力解法(嵌套循环),虽然简单易懂,但由于其时间复杂度为 \( O(n^2) \),不适合大规模数据集的需求。而通过归并排序实现逆序对计数,则能将时间复杂度降低到 \( O(n\log n) \)[^2]。 --- #### 实现细节 以下是基于归并排序的逆序对计数的具体逻辑: 1. **分解阶段** 将原始数组不断划分为两部分,直到每部分仅剩下一个元素为止。这一过程类似于标准的归并排序拆分操作。 2. **合并阶段** 合并已排序的子数组,在此期间完成逆序对的统计工作。具体来说: - 假设当前正在比较左半部分索引 `i` 和右半部分索引 `j` 的值; - 如果发现左侧元素较大 (`arr[i] > arr[j]`),则说明从位置 `i` 到该子数组结束的所有元素都与右侧当前位置上的元素形成逆序对; - 计算这些逆序对的数量,并将其累加至全局变量中。 3. **返回结果** 当整个数组重新组合完毕后,最终得到的就是总的逆序对数目。 --- #### Python代码示例 下面是一个完整的Python程序用于演示上述方法: ```python def merge_and_count_split_inv(left, right): result = [] i = j = inv_count = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) # Count the number of split inversions. inv_count += (len(left) - i) j += 1 result.extend(left[i:]) result.extend(right[j:]) return result, inv_count def sort_and_count(array): if len(array) <= 1: return array, 0 mid = len(array) // 2 left_sorted, left_inversions = sort_and_count(array[:mid]) # Recursive call on left half right_sorted, right_inversions = sort_and_count(array[mid:]) # Recursive call on right half merged_array, split_inversions = merge_and_count_split_inv(left_sorted, right_sorted) total_inversions = left_inversions + right_inversions + split_inversions return merged_array, total_inversions if __name__ == "__main__": test_array = [2, 4, 1, 3, 5] _, num_of_inversions = sort_and_count(test_array) print(f"The number of inversion pairs is {num_of_inversions}.") ``` --- #### 结果解释 以上代码实现了归并排序的同时也完成了逆序对的统计功能。测试用例 `[2, 4, 1, 3, 5]` 中存在三对逆序关系 `(2,1), (4,1)` 及 `(4,3)` ,因此输出应显示总共有三个逆序对。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值