题目描述
给定 m m m 个序列,每个包含 n n n 个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有 m m m 个整数的序列。
很明显,我们一共可以得到 n m n^m nm 个这种序列,然后我们可以计算每个序列中的数字之和,并得到 n m n^m nm 个值。
现在请你求出这些序列和之中最小的 n n n 个值。
题解
我们首先只考虑m=2的情况
只有两个串,我们要去找最小的n个值
首先我们将这两个串从小到大排序
那么最小值一定为
a
1
+
b
1
a_1+b_1
a1+b1
我们去思考次小值,它可能是
a
1
+
b
2
a_1+b_2
a1+b2或是
a
2
+
b
1
a_2+b_1
a2+b1
再往后想,其实就是a中的下标+1,或者b中的下标+1
所以我们就可以建立优先队列来自动排大小了
首先我们放入两个下标
1
,
1
1,1
1,1
然后取出堆首,把
i
+
1
,
j
i+1,j
i+1,j和
i
,
j
+
1
i,j+1
i,j+1放入堆中
思考一个问题,下一步我们有必要将上面两个数分叉出来的四种情况都放入堆吗?
分叉出来的情况,一定比当前情况要大,就比如
a
2
+
b
2
a_2+b_2
a2+b2一定不小于
a
2
+
b
1
a_2+b_1
a2+b1,因为排序后的
b
2
>
=
b
1
b_2>=b_1
b2>=b1
而如果当前这种情况都还没有处理,后面的情况比它还小,就更不会被处理到,所以当一种情况被处理后,我们再将它分叉的两种情况放入堆中
不难发现,当
a
1
,
b
2
a_1,b_2
a1,b2和
a
2
,
b
1
a_2,b_1
a2,b1分叉时,都会触碰到
a
2
,
b
2
a_2,b_2
a2,b2这个点,这样就造成了重复,重复是对我们最后的结果有影响的。为了避免重复,我们要特意排除掉其中一种情况,例如我们只让
b
b
b中下标加1作为候选项,那么如果上次操作移动了
b
b
b的下标,那么下次就不能移动b的下标,这样就是
b
b
b的下标永远不会大于
a
a
a的下标,也就不会出现重复情况了
这个操作我们也可以放入优先队列中作为变量实现
具体方法见代码
code
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int a[N],b[N],ans[N];
struct node
{
int x,y;
bool f;
bool operator<(const node &z)const{
return ans[x]+a[y]>ans[z.x]+a[z.y];
}
};
priority_queue<node> q;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++) scanf("%d",&ans[i]);
sort(ans+1,ans+1+n);
for(int i=2;i<=m;i++)
{
for(int j=1;j<=n;j++) scanf("%d",&a[j]);
sort(a+1,a+1+n);
node c;
while(q.size())q.pop();
c.x=1,c.y=1,c.f=0;
q.push(c);
for(int j=1;j<=n;j++)
{
c=q.top();
q.pop();
b[j]=ans[c.x]+a[c.y];
int X=c.x,Y=c.y;
bool F=c.f;
c.x=X,c.y=Y+1,c.f=1;
q.push(c);
if(!F) {c.x=X+1,c.y=Y,c.f=0;q.push(c);}
}
for(int j=1;j<=n;j++) ans[j]=b[j];
}
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
puts("");
}
return 0;
}