【UVA11997】K Smallest Sums 優先佇列的多路歸併問題
阿新 • • 發佈:2018-12-10
背景
給你個有序列表(假設非降序),將其合併為一個列表(這為《演算法導論》上堆部分一道例題)
一種策略是建立一個大小為的小根堆,每個序列第一個元素入堆,標記每個元素所屬佇列.
依次取出,取出後若對應序列還有元素,則加入堆中否則不加入或者加入.
PS:歸併排序的歸併過程就可以看作是大小為的一個小根堆進行合併的操作.
問題
有個序列,每個序列有個元素。現在要在每個序列裡選一個元素出來求和,故有個和,求元素總和前小的值
分析
首先考慮兩個序列的情況(下面預設每個序列已排非降序)
會組合成種和,我們注意到最小值一定為,而第二小值為或
我們可以發現若第小值為那麼第小值為或者
粗略的,我們建立一個優先佇列,佇列中初始含
第次提取堆頂得第小值,再把加入.則可以保證下一次獲得第小值.
我們可以進行擴充套件:
第次提取小值,那麼將加入
下一次可以獲得第小值
但我們可以看到這樣粗略的加入會產生兩個問題:
- 一次擴充套件元素過多,如要找第小值,最後堆中元素可能有個
- 相同元素重複入堆的判斷,如會來自.
我們重新考慮兩個序列,並結合路歸併,我們將那個和以為主元寫成如下形式:
我們這就變為了路歸併問題,堆的大小也穩定在,也不會有重複元素入堆,時間複雜度為
接下來的問題就是考慮如何將個序列向多個序列進行轉換
注意到:由於我們要求前小的值,而從構成個序列前小的值一定是
兩個序列前小的值與進行合併
於是就可以合併得到前小的值,再與進行合併
故問題可以通過次合併解決,時間複雜度為
#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)); } }