每行 A[i]+B[j] 有序,用最小堆多路归并 N 条有序流,弹 N 次取最小 N 个和。
启发题
启发记录: 多路归并模板题:每行有序流 + 最小堆维护头部 + 弹出补入,后续超级钢琴、异或粽子都是此模型的变体
OJ: luogu
题目 ID: P1631
难度:普及+/提高
标签:多路归并二叉堆
日期: 2026-08-05 09:50
题意
两个长度为
思路
直接枚举所有
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-05 09:50
* update_at: 2026-08-05 09:50
*/
// brute.cpp:小数据暴力解,直接生成所有 N² 个和并排序取前 N 个。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<long long> a(n), b(n);
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) cin >> b[i];
// 生成所有 N² 个和
vector<long long> sums;
sums.reserve(n * n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
sums.push_back(a[i] + b[j]);
sort(sums.begin(), sums.end());
// 输出前 N 个最小的和
for (int i = 0; i < n; i++) {
if (i) cout << " ";
cout << sums[i];
}
cout << "\n";
return 0;
}暴力把所有两两和全部生成后排序。关键瓶颈是
关键观察:固定
以官方样例
| 步骤 | 堆状态(弹出前) | 弹出 | 补入 |
|---|---|---|---|
| 1 | {(3,0,0), (7,1,0), (7,2,0)} | 3 | (6,0,1) |
| 2 | {(6,0,1), (7,1,0), (7,2,0)} | 6 | (10,0,2) |
| 3 | {(7,1,0), (7,2,0), (10,0,2)} | 7 | (10,1,1) |
弹出 3, 6, 7 即为答案。每次弹出后只补入同一条流的下一个元素,保证不遗漏也不重复。
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-05 09:50
* update_at: 2026-08-05 10:30
*/
// 多路归并:固定 A[i] 后 A[i]+B[j] 单调不降,用堆合并 N 条有序流,弹 N 次即得最小 N 个和。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
long long a[MAXN], b[MAXN]; // 两个单调不降序列
// 堆元素:(和, 行号, 列号)
struct HeapNode {
long long sum; // 和
int row; // 行号
int col; // 列号
bool operator<(const HeapNode& o) const { return sum > o.sum; } // 小根堆
};
priority_queue<HeapNode> pq;
long long ans[MAXN]; // 答案数组
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) cin >> b[i];
// 初始把每行的第一个元素 A[i]+B[0] 入堆
for (int i = 0; i < n; i++)
pq.push({a[i] + b[0], i, 0});
for (int k = 0; k < n; k++) {
HeapNode node = pq.top();
pq.pop();
ans[k] = node.sum;
// 同一行的下一个元素入堆
if (node.col + 1 < n)
pq.push({a[node.row] + b[node.col + 1], node.row, node.col + 1});
}
for (int i = 0; i < n; i++) {
if (i) cout << " ";
cout << ans[i];
}
cout << "\n";
return 0;
}复杂度
时间
总结
不要枚举
图示解析
text
矩阵 A[i]+B[j] 堆多路归并路线
B[0] B[1] B[2]
A[0] 3 6 10 行0: 3 → 6 → 10
A[1] 7 10 14 行1: 7 → 10 → 14
A[2] 7 10 14 行2: 7 → 10 → 14
弹出顺序: 3(行0) → 6(行0) → 7(行1) → ...每行是有序流,堆维护每行当前最小值。弹出某行后,该行的下一个元素入堆。这样只访问前