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 = ; i < n; ++i)
q.push(Item(A[i] + B[], ));
for(int i = ; i < n; ++i) {
Item item = q.top(); q.pop(); // 取出 A[a] + B[b]
C[i] = item.s;
int b = item.b;
if(b + < n) q.push(Item(item.s - B[b] + B[b + ], b + ));
// 加入 A[a] + B[b + 1] = s - B[b] + B[b + 1]
}
}

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

const int maxn = ;
int A[maxn][maxn]; int main()
{
int n;
while(scanf("%d", &n) == ) {
for(int i = ; i < n; ++i) {
for(int j = ; j < n; ++j) scanf("%d", &A[i][j]);
sort(A[i], A[i] + n);
}
for(int i = ; i < n; ++i) // 两两合并
merge(A[], A[i], A[], n); // (*) printf("%d",A[][]); // 输出结果
for(int i = ; i < n; ++i)
printf(" %d", A[][i]);
printf("\n");
}
return ;
}

  注意(*)处,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 = ; 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, , n) {
pq.push(Item(b[i] + c[], ));
}
For(i, , n) {
Item Min = pq.top();
pq.pop();
b[i] = Min.sum;
if(Min.idx + < n) {
pq.push(Item(Min.sum - c[Min.idx] + c[Min.idx + ], Min.idx + ));
}
}
} int main()
{
int k;
while(~scanf("%d",&k)) {
For(i, , k) {
scanf("%d", b + i);
}
sort(b, b + k);
For(p, , k) {
For(i, , k) {
scanf("%d", c + i);
}
sort(c, c + k);
_merge(b, c, k);
}
printf("%d", b[]);
For(i, , k) {
printf(" %d", b[i]);
}
puts("");
}
return ;
}

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

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

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

    UVA - 11997 id=18702" target="_blank" style="color:blue; text-decoration:none&qu ...

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

    题意:K个数组每组K个值,每次从一组中选一个,共K^k种,问前K个小的. 思路:优先队列处理多路归并,每个状态含有K个元素.详见刘汝佳算法指南. #include<iostream> #i ...

  3. UVa 11997 K Smallest Sums - 优先队列

    题目大意 有k个长度为k的数组,从每个数组中选出1个数,再把这k个数进行求和,问在所有的这些和中,最小的前k个和. 考虑将前i个数组合并,保留前k个和.然后考虑将第(i + 1)个数组和它合并,保留前 ...

  4. UVA 11997 K Smallest Sums (多路归并)

    从包含k个整数的k个数组中各选一个求和,在所有的和中选最小的k个值. 思路是多路归并,对于两个长度为k的有序表按一定顺序选两个数字组成和,(B表已经有序)会形成n个有序表 A1+B1<=A1+B ...

  5. 11997 - K Smallest Sums(优先队列)

    11997 - K Smallest Sums You’re given k arrays, each array has k integers. There are kk ways to pick ...

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

    来自<训练指南>优先级队列的例题. 题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=18702 题意:给定 ...

  7. 优先队列 UVA 11997 K Smallest Sums

    题目传送门 题意:训练指南P189 分析:完全参考书上的思路,k^k的表弄成有序表: 表1:A1 + B1 <= A1 + B2 <= .... A1 + Bk 表2:A2 + B1 &l ...

  8. 【UVA–11997 K Smallest Sums 】

    ·哦,这题要用优先队列?那大米饼就扔一个手写堆上去吧! ·英文题,述大意:       输入n个长度为n的序列(题中是k,2<=k<=750).一种结果定义为:从每个序列中都要挑选一个数加 ...

  9. uva_11997,K Smallest Sums优先队列

    #include<iostream> #include<cstdio> #include<cstring> #include<queue> #inclu ...

随机推荐

  1. 安装memcached服务 和 php 安装memcache扩展

    这是所有的命令,至于哪个命令是干嘛的自己悟去吧  ,顺便穿插一些知识点 安装libevent cd /home/ wget  http://www.monkey.org/~provos/libeven ...

  2. Hive新功能 Cube, Rollup介绍

    说明:Hive之cube.rollup,还有窗口函数,在传统关系型数据(Oracle.sqlserver)中都是有的,用法都很相似. GROUPING SETS GROUPING SETS作为GROU ...

  3. MySQL单机单实例安装脚本

    说明:使用mysql generic tar.gz包快速安装mysql 三个文件installation_of_single_mysql.sh.template_install-my.cnf.mysq ...

  4. xmlns:app

    Android自定义控件的属性,网上文章已经很多,之前看了也照着写了,其中有一个就是要自定义一个xml的命名空间后然后再给自定义属性赋值,后来发现不知道什么时候开始Android把这个改了,统一用 x ...

  5. 分布式锁实践(二)-ZooKeeper实现总结

    写在最前面 前几周写了篇 利用Redis实现分布式锁 ,今天简单总结下ZooKeeper实现分布式锁的过程.其实生产上我只用过Redis或者数据库的方式,之前还真没了解过ZooKeeper怎么实现分布 ...

  6. 20165233 实验二 Java面向对象程序设计

    20165233 实验二 Java面向对象程序设计 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验步 ...

  7. jdk1.6 eclipse kepler 中安装jda

    原因这是个比较老的版本的jad 参考:https://www.cnblogs.com/zhikou/p/8098137.html 1.在eclipse的help—>Install New Sof ...

  8. open方法读写文件

    vb使用open方法读写文件 (一)打开和关闭文件 1.顺序文件 打开顺序文件,我们可以使用Open语句.它的格式如下: Open pathname For [Input |Output |Appen ...

  9. Spring Data JPA 基本使用

    Spring Data 简化开发,支持Nosql和关系型数据库, DEMO https://github.com/easonstudy/boot-demo/tree/master/boot-sprin ...

  10. 13 并发编程-(线程)-异步调用与回调机制&进程池线程池小练习

    #提交任务的两种方式 #1.同步调用:提交完任务后,就在原地等待任务执行完毕,拿到结果,再执行下一行代码,导致程序是串行执行 一.提交任务的两种方式 1.同步调用:提交任务后,就在原地等待任务完毕,拿 ...