Codevs1245 最小的N个和 优先队列做法

1245 最小的N个和

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 钻石 Diamond



题目描述  Description

有两个长度为 N 的序列 A 和 B,在 A 和 B 中各任取一个数可以得到 N^2 个和,求这N^2 个和中最小的 N个。

输入描述  Input Description

第一行输入一个正整数N;第二行N个整数Ai 且Ai≤10^9;第三行N个整数Bi,
且Bi≤10^9

输出描述  Output Description

输出仅一行,包含 n 个整数,从小到大输出这 N个最小的和,相邻数字之间用
空格隔开。

样例输入  Sample Input

5

1 3 2 4 5 
6 3 4 1 7

样例输出  Sample Output

2 3 4 4 5

数据范围及提示  Data Size & Hint

【数据规模】 对于 100%的数据,满足 1≤N≤100000。



题意:两个序列,长度一定且相等,从两个序列中各取一个数求和,可以构成n^m个和,求其中最小的n个和。

算法分析:

  1. 暴力解法没得说,每个A中的值和每个B中的值相加,求出最小的n个即可,因为两个系列长度相等,所以复杂度O(n^2)。
  2. 暴力解法优化一下,就可以得到常数级别的优化,方法就是先将A,B都按照升序排个序,因为最小的值都在前面,所以找到前n个最小的只需在序列的前面找就行了。其中需要注意的是,最小的加第三小的并不一定小于第二小的加第二小的。
  3. 对于上面的暴力解法,再用个数据结构维护就能AC本题。上面方法的问题就是最后指出的那个,所以实际上就是动态维护最小值。解法有点像BFS,只不过就是把队列换成了优先队列。

解法:

    首先,将AB都排序,A[1]+b[1]现在一定是最小的,A[1]+b[2]和A[2]+B[1]中,有一个最小。那就把这两个情况都入队,就像BFS中拓展出来的点一样,通过优先队列维护出最小的值,弹出队就行了。出队时将这个点能拓展出的可能再全部入队,做n遍即可求出最小的n个和

假设次小和是A[2]+B[1],那么第三小的和就是A[1]+B[2],A[3]+B[1],A[2]+B[2]中的一个,若再假设最小和是A[1]+B[2],那么从这个点又能拓展出来A[2]+B[2],这个点共被拓展了两遍。所以为了避免这样的重复,规定如果这个点是由j自增拓展出来的,那就不拓展i自增的点了,否则都拓展。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int ans[100010],A[100010],B[100010];
struct node
{
	int i,j;
	bool last;
}a[100010];
bool operator<(node a,node b)//小根堆
{
	return A[a.i]+B[a.j]>A[b.i]+B[b.j];
}
priority_queue<node>q;

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&A[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&B[i]);
	sort(A+1,A+n+1);
	sort(B+1,B+n+1);
	node t,t1;
	t.i=1;t.j=1;t.last=false;
	q.push(t);
	for(int i=1;i<=n;i++)
	{
		t=q.top();
		ans[i]=A[t.i]+B[t.j];
		q.pop();
		t1.i=t.i;t1.j=t.j+1,t1.last=true;
		q.push(t1);
		if(t.last==false)
		{
			t1.i=t.i+1;t1.j=t.j;t1.last=false;
			q.push(t1);
		}
		
	}
	sort(ans+1,ans+n+1);
	for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值