0x10基本数据结构(0x17 二叉堆)例题2:序列
题意
【题意】
给定m个序列,每个包含n个非负整数。
现在我们可以从每个序列中选择一个数字以形成具有m个整数的序列。
很明显,我们一共可以得到n^m个这种序列, 然后我们可以计算每个序列中的数字之和,并得到n^m个值。
现在请你求出这些序列和之中最小的n个值。
【输入格式】
第一行输入一个整数T,代表输入中包含测试用例的数量。
接下来输入T组测试用例。
对于每组测试用例,第一行输入两个整数m和n。
接下在m行输入m个整数序列,数列中的整数均不超过10000。
【输出格式】
对于每组测试用例,均以递增顺序输出最小的n个序列和,数值之间用空格隔开。
每组输出占一行。
【数据范围】
0<m≤1000
0<n≤2000
【输入样例】
1
2 3
1 2 3
2 2 3
【输出样例】
3 3 4
题解
这道题目,我们一开始看是蒙的。
但是看了YXC巨佬的视频不就会了吗。
这道题目,我们不能直接算\(m\)个序列的\(m\)小值。
我们发现其实我们可以算出第\(1\)和第\(2\)个序列第前\(n\)小的序列和,然后把这\(n\)项当成一个新的序列。
很容易想,\(1,2,3\)的前\(n\)小序列和其实就是\(1,2\)的前\(n\)小的序列然后和\(3\)序列合并。(这个很好证)。
所以我们只要合并\(m-1\)次序列成一个序列,然后这个序列就是答案。
那么对于两个序列我们如何算出其前\(n\)小。
我们把第一个序列\(a\)排序,然后看第二个序列\(b\),很明显,第\(1\)小的肯定在\(a_{1}+b_{1},a_{1}+b_{2},...\)中产生,那么我们就建个堆,把这个丢进去。
然后对于最小的\(a_{1}+b_{i}\),那么第二小就会在上面那坨除\(a_{1}+b_{i}\)和\(a_{2}+b_{i}\)中产生,再把\(a_{2}+b_{i}\)丢进堆就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 2100
using namespace std;
struct node
{
int x,id/*表示是哪一行*/;
node(int xx,int yy){x=xx;id=yy;}
};
inline bool operator<(node x,node y){return x.x>y.x;}
priority_queue<node>ss;
int a[N][N],n,m;
int b[N],c[N],now[N];
void merge(int x1,int x2)
{
memcpy(b,a[x1],sizeof(b));memcpy(c,a[x2],sizeof(c));
while(!ss.empty())ss.pop();
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)ss.push(node(b[1]+c[i],i)),now[i]=1;
for(int i=1;i<n;i++)
{
a[x2][i]=ss.top().x;int x=ss.top().id;
ss.pop();
if(now[x]<n)ss.push(node(c[x]+b[++now[x]],x));
}
a[x2][n]=ss.top().x;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
}
if(m==1)
{
sort(a[1]+1,a[1]+n+1);
for(int i=1;i<n;i++)printf("%d ",a[m][i]);
printf("%d\n",a[m][n]);
continue;
}
for(int i=2;i<=m;i++)merge(i-1,i);
for(int i=1;i<n;i++)printf("%d ",a[m][i]);
printf("%d\n",a[m][n]);
}
return 0;
}