给定m个序列,每个包含n个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有m个整数的序列。
很明显,我们一共可以得到nm个这种序列, 然后我们可以计算每个序列中的数字之和,并得到nm个值。
现在请你求出这些序列和之中最小的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
思路:
可以确定的点是前n个最小数列的前2个数列的对应数字和,在前两个数列取出两个数的前n个和之中。
否则,一定有一个办法,找到一个更小的替换办法替换掉前两个数列的对应数。
那么可以先求出前2个数的前n小,再由这n个数和第3个数匹配,以此类推。
那么考虑,对于两个数列的情况,如何求2个数和的前n小。这个过程可以列成n*n的数表,并且保证数列是递增顺序的,这样可以以动态规划状态来看待,定义c[k]为第k小的数,
那么转移方程为c[k] = min{已经计算出的f[x][y]}
优先队列则是用来优化这个转移过程,维护已经计算出的
。
当第k小为
的时候,放进优先队列的数位
和
。
换言之
可以从
和
转移过来。
但这样会出现重复。
我们可以设置成
只能从
转移过来,这样的话当需要取出
的时候,
因为比
小,一定已经被计算过了。这个过程只有y = 1的时候才需要计算
,因为在需要
的时候
必须已经计算出来了,而y = 1的时候就只能从
转移了。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;
const int maxn = 2005;
int a[maxn],b[maxn],c[maxn];
int m,n;
struct Node
{
int val,x,y;
bool operator < (const Node &rhs)const
{
return val > rhs.val;
}
};
priority_queue<Node>Q;
void solve(int i)
{
for(int i = 1;i <= n;i++)scanf("%d",&b[i]);
while(!Q.empty())Q.pop();
sort(b + 1,b + 1 + n);
Q.push(Node{a[1] + b[1],1,1});
for(int i = 1;i <= n;i++)
{
Node tmp = Q.top();Q.pop();
c[i] = tmp.val;int x = tmp.x,y = tmp.y;
if(y == 1)Q.push(Node{a[x + 1] + b[y],x + 1,y});
Q.push(Node{a[x] + b[y + 1],x,y + 1});
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&m,&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
sort(a + 1,a + 1 + n);memcpy(c,a,sizeof(a));
for(int i = 2;i <= m;i++)
{
solve(i);
memcpy(a,c,sizeof(c));
}
for(int i = 1;i <= n;i++)
{
printf("%d ",c[i]);
}
printf("\n");
}
return 0;
}