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个和。
算法分析:
- 暴力解法没得说,每个A中的值和每个B中的值相加,求出最小的n个即可,因为两个系列长度相等,所以复杂度O(n^2)。
- 暴力解法优化一下,就可以得到常数级别的优化,方法就是先将A,B都按照升序排个序,因为最小的值都在前面,所以找到前n个最小的只需在序列的前面找就行了。其中需要注意的是,最小的加第三小的并不一定小于第二小的加第二小的。
- 对于上面的暴力解法,再用个数据结构维护就能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;
}