眼红的 Medusa

GitHub跳转原题关系图返回列表

把第二个名单排序后,对第一个名单中的每个编号二分查找,按原顺序输出两个名单的交集。

OJ: luogu

题目 ID: P1571

难度:入门

标签:二分排序模拟

日期: 2026-06-18 19:19

题意

给出两个获奖名单:

  • 第一个名单:获得科技创新奖的人;
  • 第二个名单:获得特殊贡献奖的人。

要求输出同时出现在两个名单中的编号,并且输出顺序要保持第一个名单中的顺序。

思路

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

int a[1005], b[1005];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

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

    bool first = true;
    for (int i = 1; i <= n; i++) {
        bool ok = false;
        for (int j = 1; j <= m; j++) {
            if (a[i] == b[j]) {
                ok = true;
                break;
            }
        }
        if (ok) {
            if (!first) cout << ' ';
            cout << a[i];
            first = false;
        }
    }
    cout << '\n';

    return 0;
}

朴素做法是:枚举第一个名单中的每个人,再到第二个名单里线性查找。这个做法是 O(nm)O(nm),当 n,m 都达到 100000 时会超时。

本题真正需要反复回答的问题是:某个编号是否出现在第二个名单中。可以先把第二个名单排序,然后对第一个名单中的每个编号做二分查找。

rbook《二分查找》文章中的基础模型是:在有序序列中找到第一个 >= x 的位置。本题也是这样:

text
pos = lower_bound(b, x)

如果 pos 没有越界,并且 b[pos] == x,说明 x 在第二个名单中出现过,就输出 x

样例查找过程

样例中第二个名单排序后是:

text
2 8 9

这张表展示第一个名单中每个编号的判断过程。

第一个名单编号 二分查找结果 是否输出
2 找到 2
15 找不到
6 找不到
8 找到 8

所以最终输出 2 8。注意输出顺序来自第一个名单,不是排序后的顺序。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 5;
int a[MAXN], b[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

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

    sort(b + 1, b + m + 1);

    bool first = true;
    for (int i = 1; i <= n; i++) {
        // 在第二个获奖名单中查找 a[i],输出顺序仍然按第一个名单来。
        int pos = lower_bound(b + 1, b + m + 1, a[i]) - b;
        if (pos <= m && b[pos] == a[i]) {
            if (!first) cout << ' ';
            cout << a[i];
            first = false;
        }
    }
    cout << '\n';

    return 0;
}

复杂度

  • 排序第二个名单需要 O(mlogm)O(m log m)
  • 对第一个名单的每个编号做一次二分,需要 O(nlogm)O(n log m)
  • 总时间复杂度 O(mlogm+nlogm)O(m log m + n log m)
  • 空间复杂度 O(n+m)O(n + m)

总结

这题是“排序后查找集合成员”的入门题。

如果题目要求保持第一个名单的输出顺序,就不能把两个名单都排序后直接输出;正确做法是只排序用于查询的第二个名单,再按第一个名单原顺序逐个判断。