P3765k个最小和
时间限制 : - MS 空间限制 : 65536 KB
评测说明 : 时限1000ms
问题描述
有k个整数数组,各包含k个元素,从每个数组中选取一个元素加起来,可以得到k^k个和,求这些和中最小的k个值。
输入格式
第一行,一个整数k(k<=500)
接下来k行,每行k个正整数(<=1000000)
输出格式
一行,k个有小到大排列的整数,表示最小的k个和
样例输入 1
3
1 8 5
9 2 5
10 7 6
样例输出 1
9 10 12
样例输入 2
2
1 1
1 2
样例输出 2
2 2
提示
样例1说明:
选出的三组数分别是(1 2 6) (1 2 7) (1 5 6)
来源 改编自uva11997
no fuck to say
题解
其实这道题目没什么特别难的
我说一个词 优先队列
是不是感觉抓到了一点东西
详细解法
首先简化题目 假设只有两个数列
数列1 2 5 3 7 8 9
数列2 5 8 6 4 7 5
和题目中要求一样 求各取一个元素后的最小和
解法十分简单
排个序
数列1 2 3 5 7 8 9
数列2 4 5 5 6 7 8
优先队列 存入数列1每一个数字加上数列2中最小数字的和
即 加入 2+4 3+4 5+4 7+4 8+4 9+4
这个时候毋庸置疑 2+4是最小的
接下来的操作十分重要
将 2+4取出 并加入 2+5 即加入 第一列第一个数字 + 第二列第一个数字
为什么这么做呢?
首先 你能保证当前队列中的最小值一定是最小的
因为当前数列中的数字是数列1当中的数字 加上 数列2中最小的数字的和
第二列没有数字能使这些和更小
但是取出 2+4 之后 你需要做的是 使包含2的项继续存在于队列中
对于 2 来讲 它的和的大小排列为
2+4 2+5 2+5 2+6 2+7 2+8
取出了2+4之后 就应该加入 2+5 因为这是次大的
解释的并不是很清楚 反正就是使数列1中的每一项都有一个最小和在单调队列中
你当前取出了它的最小和 就要加入它的次小和
所以说 单调队列中的项其实使 数列1中的每一个数字 加上当前它能够加上的最小的数列2中的数字 产生的当前的最小和
之所以要说使能够加上的 是因为之前已经加过的就不能加了
以此类推
将两行数列和中的前k个数存入 数组1 当中
然后将第三行的数字存入数组2
再次操作之后就得出了前三行数字相加得到的前k小值
于是在不停的重复之后 最后得到的就是k行和的最小值
附上对拍代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
inline int input()
{
char c=getchar();int o;
while(c>57||c<48)c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return o;
}
int A[512],B[512],sum=0,k,pos;
struct nums
{
int n,p;
bool operator <(const nums& b)const
{
return n>b.n;
}
}add;
void remove()
{
priority_queue<nums>st;add.p=1;
for(int i=1;i<=k;i++)add.n=A[i]+B[1],st.push(add);
A[1]=st.top().n;
add=st.top();st.pop();
add.p=2;add.n+=(B[2]-B[1]);
st.push(add);
for(int p=2;p<=k;p++)
{
add=st.top();st.pop();
A[p]=add.n;
add.n+=(B[add.p+1]-B[add.p]);add.p++;
if(add.p<=k)st.push(add);
}
}
int main()
{
k=input();
for(int a=1;a<=k;a++)A[a]=input();
sort(A+1,A+k+1);
for(int a=2;a<=k;a++)
{
for(int b=1;b<=k;b++)B[b]=input();
sort(B+1,B+k+1);
remove();
}
for(int i=1;i<=k;i++)printf("%d ",A[i]);
}