[算法学习笔记] 浅谈二路归并&双指针&归并排序

发布时间 2023-09-21 22:38:51作者: SXqwq

二路归并 · 双指针 是一种优化思想。它可以在 \(O(n)\) 的复杂度下把两个长度为 \(n\) 的有序数组合并为一个有序数组。

它的具体处理方法如下:

定义两个长度为 \(n\) 的升序数组 \(a,b\)。,合并完后长度为 \(2n\) 的数组 \(c\),初始化两个指针 \(x=y=1\)(这里数组下标从 \(1\) 开始)

  • 如果 \(a_x < b_y\),则 \(c_{cnt++}=a_x\),同时 \(x++\)

  • 如果 \(a_x > b_y\),则 \(c_{cnt++}=b_y\),同时 \(y++\)

正确性显然,由于我们确保 \(a,b\) 数组升序,故如果 \(a_x < b_y\) ,则 \(b\) 数组从 \(y\) 以后的数一定大于 \(a_x\),所以按照顺序取 \(a_x\),同时移动 \(x++\)

对于 \(a_x > b_y\) 同理。

由此可见,对于把一个长度为 \(n\) 的数组排序,我们可以分治,然后不断二路归并。这就是归并排序。

双指针思想应用广泛,接下来我们给出几个例题。

例题 · Luogu P1309 瑞士轮

经典永流传之瑞士轮。后面好多题目都用到了类似于瑞士轮的思想。

Link:https://www.luogu.com.cn/problem/P1309

我们发现每轮比赛都需要进行结构体排序,本题的复杂度显然无法接受。

注意到每轮比赛后胜利和失败是唯一的,不妨记录每轮比赛后胜利和失败的人,由于我们一开始确保数组有序,所以胜利和失败的人分数一定是单调递增的,可以二路归并。

实现
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 10010
using namespace std;
int n, m, k;
int s[N], w[N], q[N], q0[N], q1[N];
bool cmp(int a, int b)
{
    if (s[a] != s[b])
        return s[a] > s[b];
    return a < b;
}
int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < n * 2; i++)
        scanf("%d", &s[i]);
    for (int i = 0; i < n * 2; i++)
        scanf("%d", &w[i]);
    for (int i = 0; i < n * 2; i++)
        q[i] = i;
    sort(q, q + n * 2, cmp);
    while (m--)
    {
        int t0 = 0, t1 = 0;
        for (int i = 0; i < n * 2; i += 2)
        {
            int a = q[i], b = q[i + 1];
            if (w[a] < w[b])
            {
                s[b]++;
                q0[t0++] = a;
                q1[t1++] = b;
            }
            else
            {
                s[a]++;
                q0[t0++] = b;
                q1[t1++] = a;
            }
        }
        int i = 0, j = 0, t = 0;
        while (i < t0 && j < t1)
            if (cmp(q0[i], q1[j]))
                q[t++] = q0[i++];
            else
                q[t++] = q1[j++];
        while (i < t0)
            q[t++] = q0[i++];
        while (j < t1)
            q[t++] = q1[j++];
    }

    printf("%d\n", q[k - 1] + 1);
}