Poj2442 Sequence
给定M个长度为N的序列,从每个序列中任取一个数求和,可以构成 N^M 个和,求其中最小的N个和。
N≤2000,M≤100。
Input
第一行给出数据的组数
每组数据先给出M,N.(0 < m <= 100, 0 < n <= 2000).
接下来M行N列,描述这个数字矩形,每个数字<=10000
Output
如题
Sample Input
1
2 3
1 2 3
2 2 3
Sample Output
3 3 4
Sol1:
先简单想一下,如果数据只有一行的话,那很明显开一个小根堆,将元素值丢进去,再输出就好了。但问题是数据有多行,于是我们一行行处理。假设它只有两行,于是我们可以将第一行数据所在的小根堆中的元素一个个拿出来。然后让它加上第二行的每一行个数字,然后设计一个大根堆。为什么是大根堆呢,这里要想清楚。
因为我们的目的是求出其和最小的N个元素,设计成一个大根堆的话,如果当前堆顶元素+第二行某个元素的和小于大根堆的堆顶值,则说明这个堆顶值是没有必要存在的,要T掉。如果设计成一个小根堆的,则不能达到这个目的。于是我们维护一个只包含N个元素的大根堆。当上面的过程全处理结束后,再将大根堆的值倒入小根堆就好了。这样就得到了前两行的,符合题意的数值了。
接下来再一行行处理就好了。
#include <bits/stdc++.h>
#include <queue>
using namespace std;
int n,m,a[4000005],b[4000005];
int main()
{
int t,flat,i,j,sum,k;
scanf("%d",&t);
while(t--)
{
int ai=1,bi=0,x,r,sum,a[2005];
scanf("%d%d",&n,&m);
priority_queue <int ,vector<int >,greater<int> >p;//小根堆
priority_queue <int ,vector<int >,less<int> >q;//大根堆
for(i=0; i<m; i++)
{
scanf("%d",&x);
p.push(x);
}
for(i=1; i<n; i++)
{
for(j=0; j<m; j++)
{
scanf("%d",&a[j]);
}
while(!p.empty()) //从小根堆中一个个取出数字
{
sum=p.top();
p.pop();
for(j=0;j<m;j++)//将sum与第i行的每个数字进行相加
{
if(q.size()==m&&q.top()>sum+a[j])
//控制大根堆中只有M个元素,且值尽可能的小
{
q.pop();
q.push(sum+a[j]);
}
else if(q.size()<m)
//如果大根堆的元素值没达到M个,就直接丢进去
{
q.push(sum+a[j]);
}
}
}
while(!q.empty())//将大根堆的元素值一个个丢到小根堆中
{
p.push(q.top());
q.pop();
}
}
flat=0;
for(i=0;i<m;i++)
{
if(flat)
{
printf(" ");
}
printf("%d",p.top());
p.pop();
flat=1;
}
printf("\n");
}
return 0;
}
//题后记
用这种方法来做有序表的求和那个题要跑20+s,但其实正常的方法10s是可以过的,因为这种方法常数太大了(对堆的各种操作太多了),正解可以看lyd书上。有个方法比这个难理解一点,但常数要小一些
Sol2:
下面这个做法非常好,利用数据的有序化,缩短时间
大致思路是:将第一行加到堆内,将第二行的数字升序排好。然后将堆中数字降序放到数组a中
将a数组的第一个数字加上第二行每个数字,结果放到一个堆中
拿出第二行的每个数字,假设为b,让b加上a数组中每个数字
(一定是倒过来加,保证加出来的数字越来越大,如果加出来的数字比大根堆的堆顶小就代替之,否则说是加不下去了直接break;
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
priority_queue<int>que;
int m,n;
int num[1111][2222];
int a[2222];
int main()
{
#ifndef ONLINE_JUDGE
freopen("G:/1.txt","r",stdin);
freopen("G:/2.txt","w",stdout);
#endif
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",&num[i][j]);
}
}
for(int i=1;i<=n;i++) //将第一行的数字压入堆
{
que.push(num[1][i]);
}
for(int i=2;i<=m;i++) //从第二行到最后一行
{
sort(num[i]+1,num[i]+1+n);
//升序排好 ?为什么呢?因为我们是要尽量取小值,所以控制最先入堆的数字值尽可能小一点
//后面再找出来的数字,如果大于它就直接break了.不错不错
for(int j=1;j<=n;j++)
//将堆中的数字放到数组,数组中的值是从大到小的,弹完后注意堆为空了
{
a[j]=que.top();
que.pop();
}
for(int j=1;j<=n;j++)
//将堆中数字加上当前行的第一个数字,这样的结果明显是符合题意的
{
que.push(num[i][1]+a[j]);
}
for(int j=2;j<=n;j++) //当前行的第二个数字到最后一个数字
{
for(int k=n;k>=1;k--)
// 与a数组的数字一个个加,注意这是个逆循环,也就是说所加的a[k]的值会越来越大
if(num[i][j]+a[k]<que.top())
{
que.pop();
que.push(num[i][j]+a[k]);
}
else //没有必要加下去了,因为后面要加的a[k]的值越来越大了
{
break;
}
}
}
for(int i=1;i<=n;i++)
{
a[i]=que.top();
que.pop();
}
for(int i=n;i>=2;i--)
{
printf("%d ",a[i]);
}
printf("%d\n",a[1]);
}
}
原文:https://www.cnblogs.com/cutemush/p/11679069.html