1. 程式人生 > >【UVA11997】K Smallest Sums 優先佇列的多路歸併問題

【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));
  }
}