序列

序列

给定 m 个序列,每个包含 n 个非负整数。

现在我们可以从每个序列中选择一个数字以形成具有 m 个整数的序列。

很明显,我们一共可以得到 nm 个这种序列,然后我们可以计算每个序列中的数字之和,并得到 nm 个值。

现在请你求出这些序列和之中最小的 n 个值。

输入格式

第一行输入一个整数 T,代表输入中包含测试用例的数量。

接下来输入 T 组测试用例。

对于每组测试用例,第一行输入两个整数 mn

接下在 m 行输入 m 个整数序列,数列中的整数均不超过 10000

输出格式

对于每组测试用例,均以递增顺序输出最小的 n 个序列和,数值之间用空格隔开。

每组输出占一行。

数据范围

0<m1000,
0<n2000

输入样例:

1
2 3
1 2 3
2 2 3

输出样例:

3 3 4

 

解题思路

  题目就是给定m个序列,每个序列的长度都是n,然后从每个序列中选出一个数组成一个新序列,求各个新序列的和并输出前n个最小的和。

  可以把这个过程看作是把m个序列合并成一个长度为n的序列。如果直接合并m个序列并不好做,因此我们可以每次合并两个序列,合并m1次后这m个序列就被合并了。比如第一次先合并第1个和第2个序列,合并就是在这两个序列中每个序列都选出一个数,然后求和,一共会得到n2个和。因为求的是前n个最小的和,因此只保留前n个和。然后用这个长度为n的新序列继续与原本第3个序列进行合并的操作,以此类推,一共进行m1次。

  如果只有两个序列,当进行合并的时候,如果直接暴力来把n2个数全部求出来,再排序选出前n个数,那么每一次合并的时间复杂度都是O(n2),那么整个算法的时间复杂度就是O(m×n2),一定会超时的。

  做法是分组,考虑要合并两个序列ab,先将序列a排好序,因为有n2个和,把这些和分成n组。

{b1+a1, b1+a2, , b1+anb2+a1, b2+a2, , b2+an                           bn+a1, bn+a2, , bn+an

  因为序列a是从小到大排好序的,每一组都加上同一个数,因此每一组都是从小到大排好序的,所以每一组中的第一个数是当前组中最小的那一个数,因此这n2个数中最小的数就在每一组的第一个数中(也就是从每一组的最小数中选一个最小的数)。把每一组的第一个数选出来放到一个集合中,假设一开始这n个数中最小的是b2+a1,那么我们就把b2+a1从这些数中删掉,把b2+a2加进来(这个数在组中的下一个数),那么第2小数仍然是从这n个数的集合中选最小的数(从每一组的最小数中选一个最小的数,而当前第2组中最小的数是b2+a2)。以此类推,一共选出n个数为止。可以发现维护这个集合的操作可以用堆来实现。

  这个过程可以看作是n路归并,只不过是用堆来实现。

  AC代码如下,时间复杂度为O(T×mnlogn)

复制代码
 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 @   onlyblues  阅读(138)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示