优先队列 - Sequence - POJ 2442
给定m个序列,每个包含n个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有m个整数的序列。
很明显,我们一共可以得到nm个这种序列, 然后我们可以计算每个序列中的数字之和,并得到nm个值。
现在请你求出这些序列和之中最小的n个值。
输入格式
第一行输入一个整数T,代表输入中包含测试用例的数量。
接下来输入T组测试用例。
对于每组测试用例,第一行输入两个整数m和n。
接下在m行输入m个整数序列,数列中的整数均不超过10000。
输出格式
对于每组测试用例,均以递增顺序输出最小的n个序列和,数值之间用空格隔开。
每组输出占一行。
数据范围
0
<
m
≤
1000
,
0
<
n
≤
2000
0<m≤1000, 0<n≤2000
0<m≤1000,0<n≤2000
输入样例:
1
2 3
1 2 3
2 2 3
输出样例:
3 3 4
分析:
我 们 要 从 m 个 长 度 为 n 的 序 列 中 各 挑 选 m 个 数 得 到 新 序 列 再 求 和 , 再 从 和 的 序 列 中 挑 选 前 n 小 的 数 , 我们要从m个长度为n的序列中各挑选m个数得到新序列再求和,再从和的序列中挑选前n小的数, 我们要从m个长度为n的序列中各挑选m个数得到新序列再求和,再从和的序列中挑选前n小的数,
等 价 于 先 求 和 , 再 从 m 个 和 的 序 列 中 挑 选 n 个 数 组 合 成 新 的 序 列 , 输 出 新 序 列 前 n 小 的 数 。 等价于先求和,再从m个和的序列中挑选n个数组合成新的序列,输出新序列前n小的数。 等价于先求和,再从m个和的序列中挑选n个数组合成新的序列,输出新序列前n小的数。
而 合 并 m 个 序 列 , 可 以 两 两 合 并 m − 1 次 , 每 次 两 两 合 并 产 生 n 2 个 数 , 保 留 前 n 小 的 数 再 继 续 合 并 。 而合并m个序列,可以两两合并m-1次,每次两两合并产生n^2个数,保留前n小的数再继续合并。 而合并m个序列,可以两两合并m−1次,每次两两合并产生n2个数,保留前n小的数再继续合并。
暴 力 合 并 共 操 作 ( m − 1 ) n 2 次 , 考 虑 优 化 合 并 方 式 。 暴力合并共操作(m-1)n^2次,考虑优化合并方式。 暴力合并共操作(m−1)n2次,考虑优化合并方式。
考 虑 合 并 序 列 : a 1 , a 2 , . . . , a n 和 b 1 , b 2 , . . . , b n 考虑合并序列:\\a_1,a_2,...,a_n和b_1,b_2,...,b_n 考虑合并序列:a1,a2,...,an和b1,b2,...,bn
我 们 首 先 将 序 列 a 从 小 到 大 排 序 , 接 着 将 两 个 序 列 组 合 而 成 的 n 2 个 数 分 组 : 我们首先将序列a从小到大排序,接着将两个序列组合而成的n^2个数分组: 我们首先将序列a从小到大排序,接着将两个序列组合而成的n2个数分组:
1 ) 、 a 1 + b 1 , a 2 + b 1 , . . . , a n + b 1 2 ) 、 a 1 + b 2 , a 2 + b 2 , . . . , a n + b 2 . . . n ) 、 a 1 + b n , a 2 + b n , . . . , a n + b n 1)、a_1+b_1,a_2+b_1,...,a_n+b_1\\2)、a_1+b_2,a_2+b_2,...,a_n+b_2\\...\\n)、a_1+b_n,a_2+b_n,...,a_n+b_n 1)、a1+b1,a2+b1,...,an+b12)、a1+b2,a2+b2,...,an+b2...n)、a1+bn,a2+bn,...,an+bn
因 为 序 列 a 以 按 从 小 到 大 顺 序 排 好 , 所 以 每 一 组 的 第 一 个 数 就 是 该 组 的 最 小 值 , 我 们 先 挑 出 n 组 的 最 小 值 : 因为序列a以按从小到大顺序排好,所以每一组的第一个数就是该组的最小值,我们先挑出n组的最小值: 因为序列a以按从小到大顺序排好,所以每一组的第一个数就是该组的最小值,我们先挑出n组的最小值:
1 ) 、 a 1 + b 1 2 ) 、 a 1 + b 2 . . . k ) 、 a 1 + b k . . . n ) 、 a 1 + b n 1)、a_1+b_1\\2)、a_1+b_2\\...\\k)、a_1+b_k\\...\\n)、a_1+b_n 1)、a1+b12)、a1+b2...k)、a1+bk...n)、a1+bn
那 么 这 n 2 个 数 中 的 最 小 的 数 一 定 在 这 n 个 数 中 , 我 们 假 设 a 1 + b k 是 最 小 的 , 1 ≤ k ≤ n 。 那么这n^2个数中的最小的数一定在这n个数中,我们假设a_1+b_k是最小的,1≤k≤n。 那么这n2个数中的最小的数一定在这n个数中,我们假设a1+bk是最小的,1≤k≤n。
挑 出 a 1 + b k 后 , 第 k 组 的 最 小 值 就 变 成 了 a 2 + b k 。 挑出a_1+b_k后,第k组的最小值就变成了a_2+b_k。 挑出a1+bk后,第k组的最小值就变成了a2+bk。
于 是 n 组 中 的 最 小 值 变 成 了 于是n组中的最小值变成了 于是n组中的最小值变成了
1 ) 、 a 1 + b 1 2 ) 、 a 1 + b 2 . . . k ) 、 a 2 + b k . . . n ) 、 a 1 + b n 1)、a_1+b_1\\2)、a_1+b_2\\...\\k)、a_2+b_k\\...\\n)、a_1+b_n 1)、a1+b12)、a1+b2...k)、a2+bk...n)、a1+bn
这 样 , 继 续 从 以 上 n 个 数 中 挑 选 最 小 的 数 , 就 得 到 了 这 n 2 个 数 中 的 次 小 数 。 这样,继续从以上n个数中挑选最小的数,就得到了这n^2个数中的次小数。 这样,继续从以上n个数中挑选最小的数,就得到了这n2个数中的次小数。
以 此 类 推 , 重 复 进 行 n 次 操 作 即 可 。 以此类推,重复进行n次操作即可。 以此类推,重复进行n次操作即可。
这 个 过 程 可 以 用 小 根 堆 来 维 护 , 时 间 复 杂 度 O ( n l o g n ) 。 这个过程可以用小根堆来维护,时间复杂度O(nlogn)。 这个过程可以用小根堆来维护,时间复杂度O(nlogn)。
共 合 并 m − 1 次 , 总 的 时 间 复 杂 度 为 O ( m n l o g n ) 。 共合并m-1次,总的时间复杂度为O(mnlogn)。 共合并m−1次,总的时间复杂度为O(mnlogn)。
具体落实:
小 根 堆 以 两 个 序 列 的 和 为 第 一 关 键 字 , 由 于 挑 选 某 个 数 后 , 要 将 其 后 的 数 加 入 堆 中 , 因 此 还 需 存 储 对 应 的 下 标 。 小根堆以两个序列的和为第一关键字,由于挑选某个数后,要将其后的数加入堆中,因此还需存储对应的下标。 小根堆以两个序列的和为第一关键字,由于挑选某个数后,要将其后的数加入堆中,因此还需存储对应的下标。
用 p a i r 来 存 储 。 用pair来存储。 用pair来存储。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#define P pair<int,int>
#define x first
#define y second
using namespace std;
const int N=2010;
int n,m,T;
int a[N],b[N],c[N];
void merge() //将序列a和b组合后挑出前n小的数存储到a中
{
priority_queue<P,vector<P>,greater<P> > heap;
for(int i=0;i<m;i++) heap.push({a[0]+b[i],0});
for(int i=0;i<m;i++)
{
P t=heap.top();
heap.pop();
int s=t.x,p=t.y;
c[i]=s;
heap.push({s-a[p]+a[p+1],p+1});
}
for(int i=0;i<m;i++) a[i]=c[i];
}
int main()
{
cin>>T;
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++) scanf("%d",&a[i]);
sort(a,a+m);
for(int i=0;i<n-1;i++)
{
for(int j=0;j<m;j++) scanf("%d",&b[j]);
merge();
}
for(int i=0;i<m;i++) printf("%d ",a[i]);
puts("");
}
return 0;
}