古典叙事 · 技术札记

序章

山河有卷
人间有声

写代码,也写长风、旧城与未熄的灯。
愿每一篇随笔,都有自己的山水与回声。
阅览随笔

归并排序好题

题目

https://www.luogu.com.cn/problem/P1908

P1908 逆序对

题目描述

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

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

Update:数据已加强。

输入格式

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

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

输出格式

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

输入输出样例 #1

输入 #1

6
5 4 2 6 3 1

输出 #1

11

说明/提示

对于 \(25\%\) 的数据,\(n \leq 2500\)

对于 \(50\%\) 的数据,\(n \leq 4 \times 10^4\)

对于所有数据,\(1 \leq n \leq 5 \times 10^5\)

请使用较快的输入输出。

应该不会有人 \(O(n^2)\) 过 50 万吧 —— 2018.8 chen_zhe。

思路

1.归并排序
就是把原来的序列均等分为两个部分,然后对这两个部分继续划分,直到无法划分为止,那么此时,我们就可以合并这个不能划分的位置,把它变成有序的,通过不断的合并,那么我们就可以实现排序。
如果我们想要将一个序列排成从小到大有序的,那么每次划分后合并时左右子区间都是从小到大排好序的,我们只需要统计右边区间每一个数分别会与左边区间产生多少逆序对即可。
例如
我们现在划分好了 [1,4,9]和[5,7,8]两个部分
所以 因为9大于8 9在这一段对应的逆序对为3

i64 pre[500005];    // 原数组
i64 sum[500005];    // 辅助数组
i64 ans = 0;//记录答案
void msort(i64 l , i64 r) {//记录左右部分
	if (l >= r)
		return;
	i64 mid = ( l + r ) / 2;
	msort(l , mid) , msort(mid + 1 , r);
	i64 i = l , j = mid + 1 , k = 1;//i和j别代表左右部分的起点
	while (i <= mid && j <= r) {
		if (pre [ i ] <= pre [ j ])
			sum [ k++ ] = pre [ i++ ];
		else {
			sum [ k++ ] = pre [ j++ ];//如果右边比左边的小,那么左边从i到mid都是逆序对
			ans += mid - i + 1;//记录答案
		}
	}
	while (i <= mid)sum [ k++ ] = pre [ i++ ];
	while (j <= r)sum [ k++ ] = pre [ j++ ];
	for (int i = 1; i <= r; i++) {
		pre [ i ] = sum [ i ];//将原数组更新为归并排好序的数组(这一段区间的答案已经计算过,所以没关系)
	}

}
AC代码
#include <iostream>
#include <map>
using namespace std;
using i64 = long long;
i64 n, m, k;
i64 pre[500005];    // 原数组
i64 sum[500005];    // 辅助数组
i64 ans = 0;        // 逆序对数量

// 归并排序求逆序对
void msort(int l, int r) {
    if (l >= r) return;
    
    int mid = (l + r) / 2;
    
    // 递归排序左右两部分
    msort(l, mid);
    msort(mid + 1, r);
    
    // 合并两个有序数组并计算逆序对
    int i = l, j = mid + 1, k = l;  // 从左到右合并
    while (i <= mid && j <= r) {
        if (pre[i] <= pre[j]) {
            sum[k++] = pre[i++];
        } else {
            sum[k++] = pre[j++];
            ans += mid - i + 1;  // 统计逆序对
        }
    }
    
    // 处理剩余元素
    while (i <= mid) sum[k++] = pre[i++];
    while (j <= r) sum[k++] = pre[j++];
    
    // 复制回原数组
    for (int i = l; i <= r; i++) {
        pre[i] = sum[i];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> pre[i];
    }
    
    msort(1, n);
    
    cout << ans;
    return 0;
}
posted @ 2025-08-19 14:56  Morphis‘  阅读(36)  评论(0)    收藏  举报

特别策划 · CINEMATIC NOTES

风沙与孤骑

风起塞外,
胜负在刀剑之前。

“把复杂拆成秩序,把未知写成答案。”
Morphis · 山河一卷 愿你从这里出发,仍能听见山风。 影像:farfarSébastien Goldberg / Unsplash