Sequence
Time Limit: 6000MS | Memory Limit: 65536K | |
Total Submissions: 6281 | Accepted: 1962 |
Description
Given m sequences, each contains n non-negative integer. Now we may select one number from each sequence to form a sequence with m integers. It's clear that we may get n ^ m this kind of sequences. Then we can calculate the sum of numbers in each sequence, and get n ^ m values. What we need is the smallest n sums. Could you help us?
Input
The first line is an integer T, which shows the number of test cases, and then T test cases follow. The first line of each case contains two integers m, n (0 < m <= 100, 0 < n <= 2000). The following m lines indicate the m sequence respectively. No integer in the sequence is greater than 10000.
Output
For each test case, print a line with the smallest n sums in increasing order, which is separated by a space.
Sample Input
1 2 3 1 2 3 2 2 3
Sample Output
3 3 4
Source
POJ Monthly,Guang Lin
首先要深入理解题意:
输入是m行*n列的矩阵。需要从每一行选一个数字,这样会构成m个数字的序列。
对这个序列求和,问最小的n个和是什么。
首先,要深刻理解,我们需要维护多少个数字。因为题目中只要求前n个最小和,我们只需要每步维护n个最小的数字即可。
比如第一行和第二行各挑一个数字求和,可以形成n*n个数字,排个序后,我们只需要保存前n个数字即可,根本不用管其余的数。第三行和上一步形成的这n个数字分别求和,又形成了n*n个数字,排个序后,再只保存前n个数字即可。因此可以逐步迭代计算。
如果按照上面的方法,写一个朴素的暴力算法,则空间复杂度为n*n,时间复杂度为(n*n + n*log(n)) * m 。可是TLE了。。
/*Source Code
Problem: 2442 User: 775700879
Memory: N/A Time: N/A
Language: G++ Result: Time Limit Exceeded
Source Code*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define oo 1000000 #define MAXV 500 * 2 using namespace std; vector
result; int temp[2000*2000+10]; int last[2010]; int line[2010]; int main() { int t; scanf("%d", &t); while (t--) { int n, m; scanf("%d%d", &m, &n); int i, j, k; for (i = 0; i < n; i++) { scanf("%d", last+i); } for (i = 1; i < m; i++) { for (j = 0; j < n; j++) { scanf("%d", line+j); } int count = 0; for (j = 0; j < n; j++) { for (k = 0; k < n; k++) { temp[count++] = last[j] + line[k]; } } sort(temp, temp+count); for (j = 0; j < n; j++) { last[j] = temp[j]; } } for (i = 0; i < n; i++) { printf("%d ", last[i]); } printf("\n"); } return 0; }
需要优化。想到用堆。因为事实上我们每步都只需要维护最小的n个数即可。那我们就维护一个大跟堆(注意这里是大跟堆),这个 堆含有固定的n个元素(意味着初始化的时候,堆中有n个元素。以后只要插入一个元素,就要删除一个元素来保证堆中元素数目恒定为n)。
这个思路有了以后,再细想一下步骤:每一步先把thisline[0] 和 [lastheap[0], lastheap[n-1]] 分别相加,形成n个数字,建堆thisheap。再用[thisline[1]-thisline[n-1]]与[lastheap[0], lastheap[n-1]]分别求和,依次与thisheap的堆顶(最大值)比较,如果发现比最大值大,则舍弃。发现比最大值小,则删除堆中最大值,将新值push到堆中。
堆的几个重要的stl操作:
stl中的堆 都是 大根堆
make_heap(v.begin(), v.end()); 将n个元素的数组建堆
pop_heap(v.begin(), v.end()); 将n个元素的堆的堆顶pop掉。此时数组中的0-n-2是一个有 n-1个元素的新堆。被pop掉的 原堆顶元素被放到了n-1的位置。因此往往在这一步 之后还需要执行 v.pop_back();
push_heap(v.begin(), v.end()); 这一步首先要 确保数组中的0-n-2是一个有n-1个元素的堆,而 且新元素已经被放置在了v[n-1]位置。调用这个操作会将v[n-1] shiftup到合适的位置。因此在这一步之前要 先执行v.push_front(x);
这么做的话,ac需要2600ms+, 如果加一个小小的优化:将thisline先排序,当发现求和大于堆顶元素时,后面的就都不用比较了。这么优化可以400+ms AC。
代码如下:
/*Source Code
Problem: 2442 User: 775700879
Memory: 724K Time: 407MS
Language: G++ Result: Accepted
Source Code*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define oo 1000000 #define MAXV 500 * 2 using namespace std; vector
result; int temp[2000*2000+10]; int last[2010]; int cur[2010]; int line[2010]; int main() { int t; scanf("%d", &t); while (t--) { int n, m; scanf("%d%d", &m, &n); int i, j, k; for (i = 0; i < n; i++) { scanf("%d", last+i); } make_heap(last, last+n); for (i = 1; i < m; i++) { for (j = 0; j < n; j++) { scanf("%d", line+j); } sort(line, line+n); for (j = 0; j < n; j++) { cur[j] = last[j] + line[0]; } //保证先向堆中插入 n 个元素。 make_heap(cur, cur+n);//建堆 for (j = 0; j < n; j++) { for (k = 1; k < n; k++) { if (last[j] + line[k] > cur[0]) break; pop_heap(cur, cur+n); //将最大的元素pop掉 shiftdown cur[n-1] = last[j] + line[k]; push_heap(cur, cur+n);//将新的元素push到堆里 shiftup //在这个循环中,堆元素始终没有发生变化,一直是n } } for (j = 0; j < n; j++) { last[j] = cur[j]; } } sort(last, last + n); for (i = 0; i < n; i++) { printf("%d ", last[i]); } printf("\n"); } return 0; }