P1631 序列合并

解题思路

暴力解法的缺陷

直接计算所有N²个和然后排序的时间复杂度是O(N²logN),对于N=1e5的情况显然不可行。

优化思路

  1. 利用序列单调性:由于A和B都是单调不降的,可以避免不必要的计算

  2. 优先队列维护:使用大根堆来维护当前找到的最小的N个和

  3. 剪枝策略:当发现当前计算的和已经大于堆顶元素时,可以提前终止内层循环

具体步骤

  1. 对两个序列进行排序(题目虽说是单调不降,但实际输入可能未排序)

  2. 初始化:将A[1]与B的所有元素相加的结果存入优先队列

  3. 遍历处理:对于A[2]到A[N],计算其与B元素的组合:

    • 如果当前和≥堆顶元素,则后续组合必然更大,可以跳过

    • 否则,替换堆顶元素

  4. 输出结果:将堆中元素取出并按升序排列

时间复杂度分析

  • 排序:O(NlogN)

  • 堆操作:外层循环N次,内层循环均摊O(logN),总O(NlogN)

  • 总体复杂度:O(NlogN),可以处理N=1e5的规模

代码注释

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N], b[N];
int ans[N];
priority_queue<int> q; // 大根堆,维护当前最小的N个和

int main() {
    // 输入处理
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) cin >> b[i];
    
    // 排序确保单调性
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + n);
    
    // 初始化:将A[1]与所有B元素的和加入堆
    for(int i = 1; i <= n; i++) {
        q.push(a[1] + b[i]);
    }
    
    // 处理A[2..N]
    for(int i = 2; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int cur_sum = a[i] + b[j];
            if(cur_sum >= q.top()) break; // 剪枝:后续的和只会更大
            q.pop();         // 移除当前最大的和
            q.push(cur_sum); // 加入更小的和
        }
    }
    
    // 将结果存入数组并排序
    for(int i = n; i >= 1; i--) {
        ans[i] = q.top();
        q.pop();
    }
    
    // 输出结果
    for(int i = 1; i <= n; i++) {
        cout << ans[i] << " ";
    }
    return 0;
}

 

posted @ 2025-05-30 15:52  CRt0729  阅读(24)  评论(0)    收藏  举报