题目大意
有两个长度都是N的序列A和B,在A和B中各取一个数相加可以得到(N^2)个和,求这(N^2)个和中最小的N个。
输入格式
- 第一行一个正整数N;
- 第二行有N个整数 表示(a_1)...(a_n) 且保证(a_i <= a_{i+1})
- 第二行有N个整数 表示(b_1)...(b_n) 且保证(b_i <= b_{i+1})
输出格式
- 输出仅一行,包含N个整数,从小到大输出这N个最小的和,相邻数字之间用空格隔开。
样例
3
2 6 6
1 4 8
3 6 7
算法分析
- 这个题不难 但是有一种很好的贪心思想值得我们学习
- 首先看到这个题 暴力的思想很容易想出来 (N^2)暴力嘛 但是如何更快呢?
- 首先我们想这样一个问题 如果我们要求的是最小的一个数 那怎么求呢? 没人会把所有的和都求出来然后sort吧 没错,最小的一个数肯定是(a_1) + (b_1)
- 那么我们的前N大可不可以用同样的贪心思想求解呢???
如果有这样一个表格表示a[i] + b[j]的和 那么就有
(b_1) | (b_2) | (b_3) | |
---|---|---|---|
(a_1) | 3 | 6 | 10 |
(a_2) | 7 | 10 | 14 |
(a_3) | 7 | 10 | 14 |
- 通过这个表格显然会有一个很easy的规律:
每个表格内的位置都比它右边和它下边的数要小 - 这也很容易证明 对于它右边的数 因为(b_{j+1}) >= (b_j) 而加上同一个(a_i) 同理向下也是一样
- 所以对于这个性质我们可以维护一个单调队列 首先把第一行的所有数扔进去 然后在里面找出最小值(显然所有数中的最小值就是第一个)
- 然后将这个数下面的那个数加进队列(因为一开始是加入了一行)通过N次这样的取出与加入操作 可以求出前N小
code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int a[maxn],b[maxn];
struct node{
int a,b,v;
node(int ta,int tb,int tv){
a = ta;
b = tb;
v = tv;
}
bool operator <(const node &A)const{
return v > A.v;
}
};
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]);
for(int i = 1;i <= n;++i){
q.push(node(i,1,a[i] + b[1]));
}
for(int i = 1;i <= n;++i){
int ta = q.top().a;
int tb = q.top().b;
int tv = q.top().v;
q.pop();
printf("%d ",tv);
q.push(node(ta,tb + 1,a[ta] + b[tb + 1]));
}
return 0;
}