《算法竞赛进阶指南》0x17 T2 Sequence

题目传送门

题目描述

给定 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+1j 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值