加载中...

信息奥赛题单|归并排序


归并排序

归并排序相关知识点请见博客:信息奥赛|归并排序🔗 / (备用)信息奥赛|归并排序🔗

如链接失效,可以在文章末尾选择下一篇博客 或 在搜索界面搜索关键词「归并排序」即可。

题单 🧑🏻‍💻

题目来源 题目名称 题目链接 备注 难度
洛谷 - P1177 排序 链接🔗 归并排序模版题 🟢
洛谷 - P1908 逆序对 链接🔗 归并排序经典应用 🟢
信息学奥赛一本通 - 1328 光荣的梦想 链接🔗 归并排序经典应用 🟢

题解 🚀

归并排序

题目信息 📚

【题目名称】洛谷 P1177 - 排序
【题目描述】

将读入的 $N$ 个数从小到大归并排序后输出。

【输入】

第一行为一个正整数 $N$。

第二行包含 $N$ 个空格隔开的正整数 $a_i$,为你需要进行排序的数。

【输出】

将给定的 $N$ 个数从小到大输出,数之间空格隔开,行末换行且无空格。

【输入样例】
5
4 2 4 5 1
【输出样例】
1 2 4 4 5
【数据范围】

对于 $20%$ 的数据,有 $1 \leq N \leq 10^3$;

对于 $100%$ 的数据,有 $1 \leq N \leq 10^5$,$1 \le a_i \le 10^9$。

【原题链接】

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


题目解析 🍉

【题目分析】

归并排序の模版题(具体分析见「归并排序」知识点介绍博客:信息奥赛|归并排序🔗 / (备用)信息奥赛|归并排序🔗

【C++ 代码】
#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
int n, a[N], tmp[N];

//归并排序
void msort(int l, int r) {
    //递归条件,和快排相同
    if (l >= r) return;

    //归并排序先二分,再合并
    int mid = l + r >> 1;
    msort(l, mid);
    msort(mid + 1, r);

    //设置双指针i和j,分别遍历两个分好的有序序列,t为临时数组指针
    int i = l, j = mid + 1, t = l;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) {
            tmp[t++] = a[i++];
        } else {
            tmp[t++] = a[j++];
        }
    }

    //若双指针未到末尾,把剩余元素添加至临时数组中
    while (i <= mid) tmp[t++] = a[i++];
    while (j <= r) tmp[t++] = a[j++];
    //拷贝临时数组元素至原数组中
    for (int i = l; i <= r; i++) a[i] = tmp[i];
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    msort(1, n);  //归并排序

    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << endl;

    return 0;
}

逆序对

题目信息 📚

【题目名称】洛谷 P1908 - 逆序对
【题目描述】

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

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

【输入】

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

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

【输出】

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

【输入样例】
6
5 4 2 6 3 1
【输出样例】
11
【数据范围】

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

对于 $50%$ 的数据,$n \leq 4 \times 10^4$。

对于所有数据,$n \leq 5 \times 10^5$

请使用较快的输入输出

【原题链接】

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


题目解析 🍉

【题目分析】

归并排序の经典应用(具体分析见「归并排序」知识点介绍博客:信息奥赛|归并排序🔗 / (备用)信息奥赛|归并排序🔗

【C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
int n, a[N], t[N];
ll cnt;  //逆序对的数量最多约为n^2,爆int

// 归并排序统计逆序对数量
void InversionCount(int l, int r) {
    if (l >= r) return;

    //归并排序递归调用
    int mid = (l + r) / 2;
    InversionCount(l, mid);
    InversionCount(mid + 1, r);

    //对已经有序的两个序列求逆序对数量
    int p1 = l, p2 = mid + 1, p3 = l;
    while (p1 <= mid && p2 <= r) {
        if (a[p1] <= a[p2]) {
            t[p3++] = a[p1++];
        } else {
            t[p3++] = a[p2++];
            cnt = cnt + (mid - p1 + 1);  //核心代码
        }
    }

    while (p1 <= mid) t[p3++] = a[p1++];
    while (p2 <= r) t[p3++] = a[p2++];
    for (int i = l; i <= r; ++i) a[i] = t[i];
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入数据
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    
    // 归并排序统计逆序对数量
    InversionCount(1, n);
    cout << cnt << endl;

    return 0;
}

光荣的梦想

题目信息 📚

【题目名称】信息学奥赛一本通 1328 - 光荣的梦想
【题目描述】

Prince 对他在这片大陆上维护的秩序感到满意,于是决定启程离开艾泽拉斯。在他动身之前,Prince 决定赋予 King_Bette 最强大的能量以守护世界、保卫这里的平衡与和谐。在那个时代,平衡是个梦想。因为有很多奇异的物种拥有各种不稳定的能量,平衡瞬间即被打破。KB 决定求助于你,帮助他完成这个梦想。

一串数列即表示一个世界的状态。

平衡是指这串数列以升序排列。而从一串无序数列到有序数列需要通过交换数列中的元素来实现。KB 的能量只能交换相邻两个数字。他想知道他最少需要交换几次就能使数列有序。

【输入】

第一行为数列中数的个数 n。第二行为 n <= 10000 个数,表示当前数列的状态。

【输出】

输出一个整数,表示最少需要交换几次能达到平衡状态。

【输入样例】
4
2 1 4 3
【输出样例】
2
【题目链接】

http://ybt.ssoier.cn:8088/problem_show.php?pid=1328


题目解析 🍉

【题目分析】

经过分析,本题就是求一个序列的逆序对个数,故选择 归并排序求解逆序对 的模版即可。

【C++ 代码】
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
int n, a[N], t[N];
ll cnt;  //逆序对的数量最多约为n^2,爆int

// 归并排序统计逆序对数量
void InversionCount(int l, int r) {
    if (l >= r) return;

    //归并排序递归调用
    int mid = (l + r) / 2;
    InversionCount(l, mid);
    InversionCount(mid + 1, r);

    //对已经有序的两个序列求逆序对数量
    int p1 = l, p2 = mid + 1, p3 = l;
    while (p1 <= mid && p2 <= r) {
        if (a[p1] <= a[p2]) {
            t[p3++] = a[p1++];
        } else {
            t[p3++] = a[p2++];
            cnt = cnt + (mid - p1 + 1);  //核心代码
        }
    }

    while (p1 <= mid) t[p3++] = a[p1++];
    while (p2 <= r) t[p3++] = a[p2++];
    for (int i = l; i <= r; ++i) a[i] = t[i];
}

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    // 读入数据
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    
    // 归并排序统计逆序对数量
    InversionCount(1, n);
    cout << cnt << endl;

    return 0;
}

参考资料 📚


文章作者: Rickyの水果摊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Rickyの水果摊 !
  目录