题意
给定M个长度为N的序列,要求从每个序列中任取1个数字,可以构成NM个和,求其中最小的N个和。
思路
首先考虑只有两个序列的情况,分别为序列A和序列B,先将序列A和序列B从小到大排序。最小的和一定是A[1]+B[1],第二小的是min(A[1]+B[2],A[2]+B[1]),假设第二小的是A[1]+B[2],那么第三小的就是min(A[2]+B[1],A[2]+B[2],A[1]+B[3])。这里我们可以看出一些规律了,最小的一定是A[1]+B[1],因为对序列进行过从小到达排序的操作,下一次最小的一定是A[1]+B[1]走一步能到达的点,也就是A[1]+B[2]或者A[2]+B[1],找到第二小的A[1]+B[2]之后,接下里找除了找过的最小的就是在之前剩下的中以及上次走一步能走到的中找一个最小的。相当与一种bfs的操作,只需要bfs的队列是个优先队列,就可以满足条件了,就满足题目条件了。
这里再看怎么从两个序列拓展到M个序列,看三个序列的情况,两个序列从每个序列中选一个数最小的N个和构成一个序列,第三个序列看作一个序列,这样子又转化为了两个序列。四个序列同理,前3个序列看作第一个序列,最后一个看作第二个序列,所以不管又多少都可以用这个策略。三个序列的情况下,在前两个序列的最小N个和与第三个序列中,每个序列任取一个数,其中最小的N个和与三个序列每个序列任取一个数字最小的N个和相同,这个是很显然的事情,所以我们的策略也就正式成立了。不断的维护两个数组,一个是a数组,一个是b数组,a代表前面几个序列最小的N个和,b为当前序列,求当前之前的所有序列的最小N个和就是求a和b两个序列的最小N个和。
代码示例
下面写法不同在a序列和b序列两个序列中找n个最小和的方法
写法1
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=2005;
int T,n,m;
int que[105][maxn];//存储序列
int a[maxn];//思路中的a数组
int b[maxn];//思路中的b数组
void search()
{
priority_queue<int,vector<int>,less<int> >q;//大根堆
for(int i=1;i<=n;i++)//在所有方案中寻找最小的n个
{
for(int j=1;j<=n;j++)
{
if(q.size()<n)
{
q.push(a[i]+b[j]);
}else
{
if(a[i]+b[j]<q.top())
{
q.pop();
q.push(a[i]+b[j]);
}else//因为b数组按照从小到大排序过,所以当前如果比堆顶大,那么后面一定比堆顶大
{
break;
}
}
}
}
for(int i=1;i<=n;i++)
{
a[n-i+1]=q.top();
q.pop();
}
}
int main()
{
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",&que[i][j]);
}
sort(que[i]+1,que[i]+1+n);//对每个序列从小到大排序
}
for(int i=1;i<=n;i++)//a数组初始化为第一个序列
{
a[i]=que[1][i];
}
for(int i=2;i<=m;i++)//循环遍历序列
{
for(int j=1;j<=n;j++)
b[j]=que[i][j];
search();
}
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
printf("\n");
}
return 0;
}
写法2
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=2005;
int T,n,m;
int p;//当前在第几个序列
int que[105][maxn];//存储序列
int a[maxn];//思路中的a数组
int b[maxn];//思路中的b数组
struct node
{
int i,j;//i为a中的序号,j为b中的序号
bool flag;
node()
{
i=j=0;
flag=0;
}
node(int x,int y,bool z)
{
i=x,j=y,flag=z;
}
bool operator < (node x) const
{
return a[this->i]+que[p][this->j]>a[x.i]+que[p][x.j];
}
};
priority_queue<node>q;
int main()
{
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",&que[i][j]);
}
sort(que[i]+1,que[i]+1+n);//对每个序列从小到大排序
}
for(int i=1;i<=n;i++)//a数组初始化为第一个序列
{
a[i]=que[1][i];
}
for(int i=2;i<=m;i++)//循环遍历序列
{
p=i;
while(q.size())//清空优先队列
q.pop();
q.push(node(1,1,0));//一开始小的一定是a[1]+b[1]
for(int j=1;j<=n;j++)//思路中的b数组在这里是que[i][y]
{
int x,y;
bool f;
x=q.top().i;
y=q.top().j;
f=q.top().flag;
//取出最小的元素
q.pop();
b[j]=a[x]+que[i][y];//这里的b[j]存储这一趟走完后a数组的值
if(!f)q.push(node(x+1,y,f));
q.push(node(x,y+1,1));
/*
将下一步能到达的点加入优先队列,这里有个f判断,因为a[1]+b[2]和a[2]+b[1]
都能到达a[2]+b[2],f表示的是上次移动的方向是不是y,如果是的话x方向不能加1
这样子就不会重复到达某一个节点了
*/
}
for(int j=1;j<=n;j++)//更新a数组
a[j]=b[j];
}
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}