序列
序列
给定 $m$ 个序列,每个包含 $n$ 个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有 $m$ 个整数的序列。
很明显,我们一共可以得到 $n^m$ 个这种序列,然后我们可以计算每个序列中的数字之和,并得到 $n^m$ 个值。
现在请你求出这些序列和之中最小的 $n$ 个值。
输入格式
第一行输入一个整数 $T$,代表输入中包含测试用例的数量。
接下来输入 $T$ 组测试用例。
对于每组测试用例,第一行输入两个整数 $m$ 和 $n$。
接下在 $m$ 行输入 $m$ 个整数序列,数列中的整数均不超过 $10000$。
输出格式
对于每组测试用例,均以递增顺序输出最小的 $n$ 个序列和,数值之间用空格隔开。
每组输出占一行。
数据范围
$0 < m \leq 1000$,
$0 < n \leq 2000$
输入样例:
1 2 3 1 2 3 2 2 3
输出样例:
3 3 4
解题思路
题目就是给定$m$个序列,每个序列的长度都是$n$,然后从每个序列中选出一个数组成一个新序列,求各个新序列的和并输出前$n$个最小的和。
可以把这个过程看作是把$m$个序列合并成一个长度为$n$的序列。如果直接合并$m$个序列并不好做,因此我们可以每次合并两个序列,合并$m-1$次后这$m$个序列就被合并了。比如第一次先合并第$1$个和第$2$个序列,合并就是在这两个序列中每个序列都选出一个数,然后求和,一共会得到$n^2$个和。因为求的是前$n$个最小的和,因此只保留前$n$个和。然后用这个长度为$n$的新序列继续与原本第$3$个序列进行合并的操作,以此类推,一共进行$m-1$次。
如果只有两个序列,当进行合并的时候,如果直接暴力来把$n^2$个数全部求出来,再排序选出前$n$个数,那么每一次合并的时间复杂度都是$O(n^2)$,那么整个算法的时间复杂度就是$O(m \times n^2)$,一定会超时的。
做法是分组,考虑要合并两个序列$a$和$b$,先将序列$a$排好序,因为有$n^2$个和,把这些和分成$n$组。
\begin{cases}
b_1 + a_1,~ b_1 + a_2,~ \dots,~ b_1 + a_n \\
b_2 + a_1,~ b_2 + a_2,~ \dots,~ b_2 + a_n \\
~~~~~~~~~~~~~~~~~~~~~~~~~~~\vdots \\
b_n + a_1,~ b_n + a_2,~ \dots,~ b_n + a_n \\
\end{cases}
因为序列$a$是从小到大排好序的,每一组都加上同一个数,因此每一组都是从小到大排好序的,所以每一组中的第一个数是当前组中最小的那一个数,因此这$n^2$个数中最小的数就在每一组的第一个数中(也就是从每一组的最小数中选一个最小的数)。把每一组的第一个数选出来放到一个集合中,假设一开始这$n$个数中最小的是$b_2 + a_1$,那么我们就把$b_2 + a_1$从这些数中删掉,把$b_2 + a_2$加进来(这个数在组中的下一个数),那么第$2$小数仍然是从这$n$个数的集合中选最小的数(从每一组的最小数中选一个最小的数,而当前第$2$组中最小的数是$b_2 + a_2$)。以此类推,一共选出$n$个数为止。可以发现维护这个集合的操作可以用堆来实现。
这个过程可以看作是$n$路归并,只不过是用堆来实现。
AC代码如下,时间复杂度为$O(T \times m \cdot nlogn)$:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef pair<int, int> PII; 5 6 const int N = 2010; 7 8 int n, m; 9 int a[N], b[N], c[N]; 10 11 void merge() { 12 priority_queue<PII, vector<PII>, greater<PII>> pq; 13 for (int i = 0; i < n; i++) { 14 pq.push({a[0] + b[i], 0}); // 把每一组的最小的数放入堆中 15 } 16 17 for (int i = 0; i < n; i++) { 18 PII t = pq.top(); 19 pq.pop(); 20 21 c[i] = t.first; // 从每组的最小数中选最小的数 22 pq.push({t.first - a[t.second] + a[t.second + 1], t.second + 1}); // 放入这个数所在组的下一个数 23 } 24 25 memcpy(a, c, sizeof(c)); 26 } 27 28 int main() { 29 int tot; 30 scanf("%d", &tot); 31 while (tot--) { 32 scanf("%d %d", &m, &n); 33 for (int i = 0; i < n; i++) { 34 scanf("%d", a + i); 35 } 36 37 sort(a, a + n); // 先对第1组排序,后面的组不需要排序 38 39 for (int i = 0; i < m - 1; i++) { 40 for (int j = 0; j < n; j++) { 41 scanf("%d", b + j); 42 } 43 merge(); // 对序列a和b进行合并,并把结果存到a中 44 } 45 46 for (int i = 0; i < n; i++) { 47 printf("%d ", a[i]); 48 } 49 printf("\n"); 50 } 51 52 return 0; 53 }
参考资料
AcWing 146. 序列:https://www.acwing.com/video/125/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16617986.html