【UVA11997】K Smallest Sums 优先队列的多路归并问题

背景

给你k个有序列表(假设非降序),将其合并为一个列表(这为《算法导论》上堆部分一道例题)

一种策略是建立一个大小为k的小根堆,每个序列第一个元素入堆,标记每个元素所属队列.

依次取出,取出后若对应序列还有元素,则加入堆中否则不加入或者加入\infty.

PS:归并排序的归并过程就可以看作是大小为2的一个小根堆进行合并的操作.


问题

N个序列,每个序列有N个元素。现在要在每个序列里选一个元素出来求和,故有N^N个和,求元素总和前N小的值

 

分析

首先考虑两个序列A[],B[]的情况(下面默认每个序列已排非降序)

A[],B[]会组合成N^2种和,我们注意到最小值一定为A[1]+B[1],而第二小值为A[1]+B[2]A[2]+B[1]

我们可以发现若第l小值为A[x]+B[y]那么第l+1小值为A[x+1]+B[y],A[x]+B[y+1]或者A[x_0]+B[y_0](x_0 \leq x \vee y_0 \leq y)

粗略的,我们建立一个优先队列,队列中初始含A[1]+B[1]

l次提取堆顶A[x]+B[y]得第l小值,再把A[x+1]+B[y],A[x]+B[y+1]加入.则可以保证下一次获得第l+1小值.

 

我们可以进行扩展:

l_0次提取A[1][x_1]+A[2][x_2]+\cdots小值,那么将A[1][x_1+1]+A[2][x_2]+\cdots,A[1][x_1]+A[2][x_2+1]+\cdots加入

下一次可以获得第l_0+1小值

但我们可以看到这样粗略的加入会产生两个问题:

  • 一次扩展元素过多,如要找第k小值,最后堆中元素可能有kN
  • 相同元素重复入堆的判断,如(3,2)会来自(2,2),(3,1).

 

我们重新考虑两个序列,并结合k路归并,我们将那N^2个和以A[]为主元写成如下形式:

\\ A[1]+B[1],A[1]+B[2],\cdots,A[1]+B[N]\\ A[2]+B[1],A[2]+B[2],\cdots,A[2]+B[N]\\ \cdots\\ A[N]+B[1],A[N]+B[2],\cdots,A[N]+B[N]\\

我们这就变为了k路归并问题,堆的大小也稳定在N,也不会有重复元素入堆,时间复杂度为O(NlogN)

 

接下来的问题就是考虑如何将2个序列向多个序列进行转换

注意到:由于我们要求前N小的值,而从构成A[],B[],C[]3个序列前N小的值一定是

A[],B[]两个序列前N小的值与C[]进行合并

于是就可以合并A[],B[]得到前N小的值A^{'}[],再与C[]进行合并

故问题可以通过N-1次合并解决,时间复杂度为O(N^2logN)

 

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
using namespace std;

int N;
int A[1005][1005];
struct node {
  int val, id;
  node(int val, int id) : val(val), id(id){
  };
};
struct cmp {
  bool operator ()(const node &a, const node &b){
    return a.val > b.val;
  }
};
void Merge(int *, int *, int *);

int main(){
  while(~scanf("%d", &N)){
    int i, j;
    for(i = 1; i <= N; i++){
      for(j = 1; j <= N; j++)
        scanf("%d", &A[i][j]);
      sort(A[i] + 1, A[i] + 1 + N);
    }
    for(i = 2; i <= N; i++) Merge(A[1], A[i], A[1]);
    for(i = 1; i <= N; i++) printf("%d%c", A[1][i], (i == N) ? '\n' : ' ');
  }
  return 0;
}

void Merge(int *A, int *B, int *C){
  priority_queue<node, vector<node>, cmp> Q;
  int i;
  for(i = 1; i <= N; i++) Q.push(node(A[i] + B[1], 1));
  for(i = 1; i <= N; i++){
    auto item = Q.top(); Q.pop();
    C[i] = item.val;
    if(item.id + 1 <= N) Q.push(node(item.val - B[item.id] + B[item.id + 1], item.id + 1));
  }
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值