给定 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;
#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;
}