算法竞赛进阶指南-60.序列
题目链接
给定 个序列,每个包含 个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有 个整数的序列。
很明显,我们一共可以得到 个这种序列,然后我们可以计算每个序列中的数字之和,并得到 个值。
现在请你求出这些序列和之中最小的 个值。
输入格式
第一行输入一个整数 ,代表输入中包含测试用例的数量。
接下来输入 组测试用例。
对于每组测试用例,第一行输入两个整数 和 。
接下在 行输入 个整数序列,数列中的整数均不超过 。
输出格式
对于每组测试用例,均以递增顺序输出最小的 个序列和,数值之间用空格隔开。
每组输出占一行。
数据范围
,
输入样例:
1
2 3
1 2 3
2 2 3
输出样例:
3 3 4
Method : 优先队列
首先只考虑两个序列:和
把数组A进行排序,得到,
分组:那么可以得到个和,写成如下组长度为的序列,那么每个序列内部都是有序的(从小到大)
那么首先从这n组序列中选出最小的数,就是比较第一列的数,不妨假设最小的数是,那么第二组的指针就向后移动,把加进来继续比较,选出第二小的数…依次类推,可以得到两个序列的前n小和。
可以用一个堆来维护,时间复杂度为
对于m个序列,我们仍然可以先只考虑两个序列,筛选出这两个序列的前n小和后,将其作为一个新的序列,继续与下一个序列求前n小和,一共需要求m - 1次,因此总时间复杂度为。
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef pair<int, int> PII; // {当前的数, 当前数所在序列的下标}
const int N = 2000 + 7;
int T;
int m, n;
int a[N], b[N], tmp[N];
void merge() {
priority_queue<PII, vector<PII>, greater<PII>> pq;
for (int i = 0; i < n; i ++) {
pq.push({a[0] + b[i], 0});
}
for (int i = 0; i < n; i ++) {
auto t = pq.top(); pq.pop();
tmp[i] = t.first;
pq.push({t.first + a[t.second + 1] - a[t.second], t.second + 1});
}
// copy
memcpy(a, tmp, sizeof tmp);
}
int main() {
cin >> T;
while (T --) {
scanf("%d %d", &m, &n);
for (int j = 0; j < n; j ++) scanf("%d", &a[j]);
sort(a, a + n);
for (int i = 1; i < m ; i ++) {
for (int j = 0; j < n; j ++) scanf("%d", &b[j]);
merge();
}
for (int i = 0; i < n; i ++) printf("%d ", a[i]);
puts("");
}
return 0;
}
复杂度分析
时间复杂度:, 其中是次堆的push() & pop(),需要进行次求前n小和操作。
空间复杂度:。
本博客所有文章均采用 CC BY-NC-SA 4.0 协议 ,禁止商用,转载请注明出处!