算法竞赛进阶指南:0x17:Sequence

原题链接

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

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

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

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

输入格式

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

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

对于每组测试用例,第一行输入两个整数 m 和 n。

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

输出格式

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

每组输出占一行。

数据范围

0<m≤1000,
0<n≤2000

输入样例:

1
2 3
1 2 3
2 2 3

输出样例:

3 3 4

题意:有m个长度为n的序列,每个序列中都要选一个元素,组成一个m个元素的序列,一共有n^m种组合方法,求前n个最小序列和;

解法:小根堆

我们可以用一个小根堆来存放序列和,假设对于a,b两个序列:

书上做法是:先将a排序,然后将最小值(a[0]+b[0])加入堆中;

然后对于每个堆顶元素(堆的最小值,假设为a[i]b[j]),将堆顶元素出堆(此时已经是堆中最小元素),用一个数组存储;

将次小元素(a[i+1]b[j],a[i]b[j+1])加入堆中,再将堆顶元素出堆,不断的重复此过程,最后可得出前n个最小序列和;

对于m个序列,只需要将已经得出的前n个最小序列和与下一个未匹配的序列重复上述过程,直到全部匹配,可得出最终n个最小序列和;

y总:

对于a,b两个序列,将a排序,直接一次性将所有a[0]+b[j](0~n-1)加入堆中,然后对于每个堆顶元素,将其a[i+1]b[j]加入堆中;

堆顶出堆,用一个组存储,不断重复上述过程,由于b已经将b[0~n-1]都加入了堆中,所以不需要再考虑a[i]b[j+1]的结果;

可得出前n个最小序列和,扩展到m个序列,由于前两个已经求出n个最小序列和,

所以扩展到第3个,只需要将求出的n个最小序列和当作a数组,第三个序列当作b数组,可求出对于三个序列的n个最小序列和;

扩展到m个,不断的将已经求出的n个最小序列和当作a数组,未匹配的数组当作b数组,可求出对于m个序列的n个最小序列和;

需要注意的是,对于堆顶元素,我们需要知道它是a数组中第几个元素(a[i]),

所以我们用pair存储数据,first存储序列和,second存储位置i;

y总讲解

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
using PII=pair<int,int>;
const int N=2e3+10;

int a[N],b[N],temp[N];
//a表示前几个序列的前n个最小序列和(最后表示m个序列的最小序列和)
//b表示未匹配的数组,temp表示中途过渡的数组
int m,n;
void work(){
    priority_queue<PII,vector<PII>,greater<PII>>heap;//小根堆pair存储序列和和下标
    for(int j=0;j<n;++j)
        heap.push({a[0]+b[j],0});//初始一次性加入a[0]+b[j](0~n-1)
    for(int j=0;j<n;++j){
        auto t=heap.top();//取出堆顶
        heap.pop();
        int sum=t.first,i=t.second;//sum存储和,i存储在a数组的位置
        temp[j]=sum;//过渡存储和
        heap.push({sum-a[i]+a[i+1],i+1});//由于b[0]~b[n-1]已经全部加入,只需加入a[i+1],b[j]
    }
    for(int i=0;i<n;++i)
        a[i]=temp[i];//将结果过渡到a数组中
}

int main()
{
    int T;
    cin>>T;
    while(T--){
        cin>>m>>n;
        for(int i=0;i<n;++i) cin>>a[i];//a数组作为起始序列
        sort(a,a+n);//排序
        for(int i=0;i<m-1;++i){
            for(int j=0;j<n;++j)
                cin>>b[j];
            work();//每次将a,b两个序列匹配
        }
        for(int i=0;i<n;++i) cout<<a[i]<<' ';//最后a数组表示最终结果
        cout<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值