眼红的 Medusa

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

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

OJ: luogu

题目 ID: P1571

难度:普及-

标签:二分排序模拟

创建: 2026-06-18 19:19

更新: 2026-10-07 10:17

相似题目
roj 1286怪盗基德的滑翔翼同难度同型题(M5 自 pre 移入;master 重新定级后两者同档):B 的二分 LIS 里 lower_bound_pos 复用的正是 A 教的「在有序序列里找第一个 >= x 的位置」这一步,只是从判断元素存在变成在 tails 上定位替换点,再叠加取负转 LIS 与正反两方向取最大这两个额外流程。roj 1260【例9.4】拦截导弹(Noip1999)同难度同型题(M5 自 pre 移入;master 重新定级后两者同档):B 的二分 LIS 直接复用 A 教的「找有序序列中第一个 >= x 的位置」做替换,只是换成在 tails 数组上维护,再叠加 Dilworth 定理完成第二问luogu P5076【深基16.例7】普通二叉树(简化版)同难度同型题(M5 自 pre 移入;master 重新定级后两者同档):B 的全部查询建立在 A 教的有序序列 lower_bound 二分定位(第一个≥x 的位置)语义上,bisect_left/right 是同一模型的左右边界版本,再叠加 insort 插入维持有序luogu P1678烦恼的高考志愿同难度同型题(M5 自 pre 移入;master 重新定级后两者同档):B 把 A 教的排序数组 lower_bound 分界点模型用作第一步,再叠加前驱比较求最近分数线atcoder abc248_dRange Count Query同难度同型题(M5 自 pre 移入;master 重新定级后两者同档):B 的查询原样复用 A 教的「lower_bound 找第一个 >=x 的位置、再判该位置值是否等于 x」这一步来划定数值 X 的块边界并判存在,再叠加 A 未教的 (数值,下标) 双关键字排序与在块内二次二分下标求 rr-ll 计数。

题意

给出两个获奖名单:

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

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

思路

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

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)。

总结

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

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