UVA 11997 K Smallest Sums 优先队列 多路合并

  vjudge 上题目链接:UVA 11997

  题意很简单,就是从 k 个数组(每个数组均包含 k 个正整数)中各取出一个整数相加(所以可以得到 k个结果),输出前 k 小的和。

  这时训练指南上的一道题,这道题的简化版其实在 15 年的广东省省赛出现过,当时是以送分题的形式出现的,可我还是没能做出来,归根到底还是看书不够,接触的题型不够多。

*************************************************************大白书上的讲解开始***********************************************************************

  在解决这个问题之前,先看看它的简化版:给出两个长度为 n 的有序表 A 和 B,分别在 A 和 B 中任取一个数并相加,可以得到 n2 个和。求这些和中最小的 n 个和。

  这个问题可以转化为多路归并排序问题:即把 k 个有序表合并成一个有序表(假定每个表已经是升序排列)—— 用优先队列维护每个表的“当前元素”。如果一共有 n 个元素,则时间复杂度为 O(nlogk)。此时我们需要把这 n个和组织成如下 n 个有序表:

  表1:A1 + B1 <= A1 + B2 <= A1 + B3 <= ....
  表2:A2 + B<= A2 + B<= A2 + B3 <= ....

  ......

  表n:An + B<= An + B<= An + B3 <= ....

  其中第 a 张表里的元素形如 Aa + B。用二元组 (s, b) 来表示一个元素,其中 s = Aa + B。为什么不保存 A 的下标 a 呢?因为我们用不到 a 的值。如果我们需要得到一个元素 (s, b) 在表 a 中的下一个元素 (s‘, b+1),只需要计算 s‘ = Aa + Bb+1 = Aa + B- Bb + Bb+1 = s - B+ Bb+1,并不需要知道 a 是多少。代码里可以用到如下结构体来表示。

struct Item {
    int s, b;   // s = A[a] + B[b]。这里的 a 并不重要,因此不保存
    Item(int s, int b): s(s), b(b) { }
    bool operator < (const Item &rhs) const {
        return s > rhs.s;
    }
};

  因为在任意时刻,优先队列中恰好有 n 个元素,一共取了 n 次最小值,因此时间复杂度为 O(nlogn)。代码如下:

//假设 A 和 B 的元素已经从小到大排序好
void merge(int *A, int *B, int *C, int n)
{
    priority_queue<Item> q;
    for(int i = 0; i < n; ++i)
        q.push(Item(A[i] + B[0], 0));
    for(int i = 0; i < n; ++i) {
        Item item = q.top();  q.pop();  // 取出 A[a] + B[b]
        C[i] = item.s;
        int b = item.b;
        if(b + 1 < n)   q.push(Item(item.s - B[b] + B[b + 1], b + 1));
        // 加入 A[a] + B[b + 1] = s - B[b] + B[b + 1]
    }
}

  而这题不是两个表,而是 k 个表,怎么办呢?两两合并就可以了(想一想,为什么),代码如下:

const int maxn = 768;
int A[maxn][maxn];

int main()
{
    int n;
    while(scanf("%d", &n) == 1) {
        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < n; ++j)  scanf("%d", &A[i][j]);
            sort(A[i], A[i] + n);
        }
        for(int i = 0; i < n; ++i)          // 两两合并
            merge(A[0], A[i], A[0], n);     // (*)

        printf("%d",A[0][0]);               // 输出结果
        for(int i = 0; i < n; ++i)
            printf(" %d", A[0][i]);
        printf("\n");
    }
    return 0;
}

  注意(*)处,merge 函数对 A[0] 又读又写,会有问题吗?(其实仔细观察函数就可以发现不会有任何影响,因为原来的值读完一次后就再也没用了,所以可以重复利用空间,便有了之后的写)。程序的复杂度为 O(k2logk)。另外,没有必要在一开始就把所有 k个元素保存在二维数组 A 中,而是可以每次只读 k 个元素,然后合并,从而大大降低空间复杂度。

*************************************************************大白书上的讲解结束***********************************************************************

  以上是大白书上的讲解,根据它的思路我自己实现了本题的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define  For(i,s,t)  for(int i = (s); i < (t); ++i)
const int K = 760;

int b[K],c[K];

struct Item
{
    int sum, idx;
    Item(int _sum, int _idx): sum(_sum), idx(_idx) {}
    bool operator < (const Item &rhs) const {
        return sum > rhs.sum;
    }
};

void _merge(int *b, int *c, int n)
{
    priority_queue<Item> pq;
    For(i, 0, n) {
        pq.push(Item(b[i] + c[0], 0));
    }
    For(i, 0, n) {
        Item Min = pq.top();
        pq.pop();
        b[i] = Min.sum;
        if(Min.idx + 1 < n) {
            pq.push(Item(Min.sum - c[Min.idx] + c[Min.idx + 1], Min.idx + 1));
        }
    }
}

int main()
{
    int k;
    while(~scanf("%d",&k)) {
        For(i, 0, k) {
            scanf("%d", b + i);
        }
        sort(b, b + k);
        For(p, 1, k) {
            For(i, 0, k) {
                scanf("%d", c + i);
            }
            sort(c, c + k);
            _merge(b, c, k);
        }
        printf("%d", b[0]);
        For(i, 1, k) {
            printf(" %d", b[i]);
        }
        puts("");
    }
    return 0;
}

  感觉这道题蕴含的算法思想挺经典的,以后可能还会碰到,需要好好体会才行。

时间: 2024-08-08 15:11:01

UVA 11997 K Smallest Sums 优先队列 多路合并的相关文章

uva 11997 K smallest sums (优先队列 多路归并)

算法入门经典 训练指南 p189 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> using namespace std; struct Item { int s,b; Item(int s,int b) :s(s),b(b) {} bool operator < (const Item&rhs) co

UVa 11997 K Smallest Sums 优先队列&amp;&amp;打有序表&amp;&amp;归并

UVA - 11997 K Smallest Sums Time Limit: 1000MS   Memory Limit: Unknown   64bit IO Format: %lld & %llu Submit Status You're given k arrays, each array has k integers. There are k^k ways to pick exactly one element in each array and calculate the sum o

uva 11997 K Smallest Sums 优先队列处理多路归并问题

题意:K个数组每组K个值,每次从一组中选一个,共K^k种,问前K个小的. 思路:优先队列处理多路归并,每个状态含有K个元素.详见刘汝佳算法指南. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<stack> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #includ

UVA 11997 K Smallest Sums (多路归并)

从包含k个整数的k个数组中各选一个求和,在所有的和中选最小的k个值. 思路是多路归并,对于两个长度为k的有序表按一定顺序选两个数字组成和,(B表已经有序)会形成n个有序表 A1+B1<=A1+B2 A2+B1<=A2+B2 ... An+B1<=An+B2 在学习的归并排序的时候是把两个有序的表合并成一个,每次比较只在两个元素之间进行,所以只需要用>比较, 而现在需要同时合并n个有序表,优先队列(堆)就派上用场了.类似归并排序用i和j维护有序表当前考虑元素, 合并的时候,每次取出的

UVA 11997 K Smallest Sums 优先队列+归并 STL

题目链接: UVA...... 题目描述: 有K组数, 每组数有K个, 所以每组数各选一个加和有k^k种情况, 要求输出其中的最小的前k种, 从小到大输出 解题思路: 首先对于两个数组取前K个, 构造二元组(s, b) 其中s = Aa + Bb , a, b 为下标. 为什么不用三元组(s,a,b)呢, 因为二元组完全可以表示三元组, 下一个元素就是s+B[i+1]-B[i] . 我们需要把这k^2个和组织成如下k个有序表.(A, B是有序的哦) 表1:A1+B1<=A1+B2<=.....

【优先队列之多路合并】UVA - 11997 K Smallest Sums

Source : UVA - 11997 K Smallest Sums http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18702 题意 有k个整数数组,各包含k个元素,从每个数组中选取一个元素加起来,可以得到k^k个和,求这些和中最小的k个值. 示例 Sample Input 3 1 8 5 9 2 5 10 7 6 2 1 1 1 2 Sample Output 9 10 12 2 2 思路 二路归并构建二维平面表:

11997 - K Smallest Sums(优先队列)

11997 - K Smallest Sums You’re given k arrays, each array has k integers. There are kk ways to pick exactly one element in eacharray and calculate the sum of the integers. Your task is to find the k smallest sums among them.InputThere will be several

(DS 《算法竞赛入门经典》)UVA 11997 K Smallest Sums

题目大意:有k个数组,每个数组选取一个数,组成k^k个数.在这k^k个数中选择最小的前k个数 解题思路: 1.如果只有k个数组,那么最后得到的最小的前k个数应该可以由前两个数组得到的最小k个数与第三个数组 按规则运算后得到. 2.如果每个数组只有3个数.那么前两个数组(a:(a0,a1,a2)    b:(b0,b1,b2,a与b数组都已经有序)运算后有的结果矩阵如下: a0+b0,a0+b1,a0+b2 a1+b0,a1+b1,a1+b2 a2+b0,a2+b1,a2+b2 在这个矩阵中,a0

【UVA 11997 K Smallest Sums】优先级队列

来自<训练指南>优先级队列的例题. 题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18702 题意:给定k个整数数组,各包含k个元素.在每个数组中取一个元素加起来,可以得到kk个和,求这些和中最小的k个值(不去重). 数据范围:k [2, 750] 思路:暴力枚举k^k不可取. “先来看问题的简化版:给出两个长度为k的数组A和B,分别在A和B中任取一个数并相加,可以得到k^2个和,求这些和中最小的k个.” 首先