P1631 序列合并

题目大意

有两个长度都是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;
}
posted @ 2020-07-24 08:00  HISKrrr  阅读(131)  评论(0编辑  收藏  举报