序列

序列

给定 $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/

posted @ 2022-08-23 21:58  onlyblues  阅读(133)  评论(0编辑  收藏  举报
Web Analytics