openjudge 百练2442:Sequence

题目描述

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?

中文描述

给定m个数字序列,每个序列包含n个非负整数。我们从每一个序列中选取一个数字组成一个新的序列,显然一共可以构造出n的m次方个新序列。接下来我们对每一个新的序列中的数字进行求和,一共会得到n的m次方个和,请找出最小的n个和。其中 0 < m <= 100 , 0 < n <= 2000。

解题思路

可以看到m和n都比较大,如果直接用暴力计算的话,复杂度是100^2000,肯定不可行。那么如果采用递归的思想,假设给定了前m-1个序列的最小的n个和,能不能计算出前m个序列的最小n个和呢?

问题等价于给定两个序列,从每个序列中各取一个数求和,然后取前n个最小的结果。首先把这两个序列从小到大排序,这时候就可以利用堆来优化,首先我们把第一个序列的每一个数都加上第二个序列的第一个数然后放入大顶堆中,这时候这个大顶堆中刚好有n个元素。

然后我们再依次把第一个序列中的每个数依次加上第二个序列中的后续的数,(注意这里外层循环是第二个序列的数,内层循环是第一个序列的数),和大顶堆中的堆顶元素进行比较:

  • 如果堆顶元素更大,那么结果就可以放入堆中并且弹出一个最大元素,以此保证堆中元素始终是n个。

  • 如果堆顶元素小,那么第一个序列中后面的元素就不需要再跟第二个序列中的当前数相加再跟堆顶元素比较了,因为结果一定比当前的这个和更大,这样就能跳出内层循环,实现了剪枝。

有了计算两个序列的最小n个和的方法,把这n个和当作一个新的序列和第三个序列求最小n个和,就能计算出三个序列的最小n个和,然后就能计算出第四个……,再一直推广下去就能求出m的序列的最小n个和了。

AC代码

#include <iostream>
#include <vector>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;

int main()
{
    int T;
    cin>>T;
    while(T-->0){
        int m,n;
        cin>>m>>n;
        // 题目数据较多,使用再读入可以节省空间
        vector<int> data;
        for(int i=0;i<n;++i){
            int tp;
            scanf("%d", &tp);
            data.push_back(tp);
        }
        sort(data.begin(), data.end());
        // pre_q保存前m-个序列的最小的n个和
        // cur_q保存当前正在计算的最小的n个和
        // 注意使用大顶堆
        priority_queue<int, vector<int>, less<int>> pre_q, cur_q;
        for(auto i:data){
            pre_q.push(i);
        }
        for(int i=1;i<m;++i){
            data.clear();
            for(int j=0;j<n;++j){
                int tp;
                scanf("%d", &tp);
                data.push_back(tp);
            }
            // 一定要排序便于剪枝,原序列没有说是有序的
            sort(data.begin(), data.end());
            // temp_q保存前i个序列,每个序列取一个数字的n个和
            // 注意使用小顶堆
            priority_queue<int, vector<int>, greater<int>> temp_q;
            while(!pre_q.empty()){
                // 保证cur_q中的数都是每个序列取一个数加起来的和
                cur_q.push(pre_q.top()+data[0]);
                temp_q.push(pre_q.top());
                pre_q.pop();
            }
            while(!temp_q.empty()){
                int add1 = temp_q.top();
                temp_q.pop();
                for(int j=1;j<n;++j){
                    int res = add1 + data[j];
                    // 剪枝操作
                    // 如果res比大顶堆中最大的数都大,那么add1加上这个序列后面的元素的和一定比res更大,
                    // 因此也比大顶堆中的数大,因此可以直接剪枝。
                    if(res>cur_q.top()) break;
                    else{
                        cur_q.pop();
                        cur_q.push(res);
                    }
                }
            }
            swap(cur_q, pre_q);
        }
        // 输出结果
        vector<int> res;
        while(!pre_q.empty()){
            res.push_back(pre_q.top());
            pre_q.pop();
        }
        for(int i=res.size()-1;i>=0;--i){
            printf("%d%c", res[i], i==0?'\n':' ');
        }
    }
    return 0;
}
posted @ 2020-03-25 10:54  heanrum  阅读(348)  评论(2)    收藏  举报