K Smallest Sums
You're given k arrays, each array has k integers. There are kk ways to pick exactly one element in each array and calculate the sum of the integers. Your task is to find the k smallest sums among them.
Input
There will be several test cases. The first line of each case contains an integer k (2<=k<=750). Each of the following k lines contains k positive integers in each array. Each of these integers does not exceed 1,000,000. The input is terminated by end-of-file (EOF). The size of input file does not exceed 5MB.
Output
For each test case, print the k smallest sums, in ascending order.
Sample Input
3
1 8 5
9 2 5
10 7 6
2
1 1
1 2
Output for the Sample Input
9 10 12
2 2 题目大意:
给定一个k*k的一个矩阵,如果让你在每一行取出一个数,再将每一行取出的数相加,那么总共可以得到k^k种相加方法,现在让你求出这k^k个结果中最小的k个结果。 分析:
咋一看,的确很难有想法,但是仔细分析这个题目我们会发现其实这个问题是满足最优子结构的,比如:
如果我们已经计算出了前m行,每行取出一个数相加的最小的k个结果,分别是DP[1],DP[2]...DP[k](注意这里的DP表示的是前m行每行一个相加的最小的前k个值)
假设第m+1行的值是A[1],A[2]...A[k] (注意这里的A[i]表示的是第m+1行的第i个数)
当我们推倒到第m+1行时,由于我们只计算了前m行的前k个最小值,那我们是不是有必要多计算一些来推导出第m+1行的前k个最小值呢, 答案是不必要的,我们可以通过以下数学公式严格证明:
设DP[x]是前m行通过计算得出的第x(x>k)小的和,如果上述的假设成立,那么我们可以列出不等式:
DP[x] + A[y] < DP[m] + A[n] (1) (DP[m]+A[n]表示只通过DP[1,2...k]计算出的前m+1行第k小的和)
上述不等式的含义是指在第m+1行存在一个数A[y],使得DP[x]+A[y]是前m+1行中前k小的结果。
同时,我们注意到: x>k ==> DP[x] > DP[k] (2)
而且: A[y] >= A[1] (3)
由上面三个不等式(1),(2),(3)我们可以得到:
DP[k]+A[1] <= DP[x]+A[y] < DP[m]+A[n]
也就是 DP[k]+A[1] < DP[m]+A[n]
之前我们说过DP[m] + A[n] 是前m行第k大的和,然而:比DP[k]+A[1]小的数已经有
(DP[1]+A[1]),(DP[2]+A[1])...(DP[k-1]+A[1])共计k-1个,
所以DP[k]+A[1]是第k个最小的和,与假设的DP[m]+A[n]是第k个最小的和相矛盾,所以假设不成立。
得证。 通过以上的证明我们可以得出结论要计算第m+1行的前k个最小和是只需要计算出前m行的前k个最小的和即可。
这时,我们的目标就转化为了计算一个2*k的数组,在第一行取一个数,在第二行取一个数,得到k^2个和,求他们当中的最小的k个和。 为了计算它,我们把这n^2个数组织成如下n个有序表:
表1: A1+B1 <= A1+B2<=A1+B3<=......
表2: A2+B1 <= A2+B2<=A2+B3<=......
.
表n: An+B1 <= An+B2<=An+B3<=...... 这时我们用一个二元组(sum, b)来保存以上的每一个元素,其中sum=A[a] + B[b].为什么不保存A的下标a呢?因为我们用不到a的值。如果我们需要在表(sum, b)中赵到下一个元素(sum', b+1),只要计算sum' = s - B[b] + B[b+1],不需要知道a是多少。
struct Item {
int sum, b; //s = A[a] + B[b]
Item(int _s, int _b)
{
sum = _s;
b = _b;
}
bool operator < (const Item& rhs) const {
return sum > rhs.sum;
}
};
至于如何合并,见代码:(其中merge的功能是将a和b数组合并到一个数组a中,最后的结果保存在a中)
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define inf -0x3f3f3f3f
#define lson k<<1, L, mid
#define rson k<<1|1, mid+1, R
#define mem0(a) memset(a,0,sizeof(a))
#define mem1(a) memset(a,-1,sizeof(a))
#define mem(a, b) memset(a, b, sizeof(a))
#define FOPENIN(IN) freopen(IN, "r", stdin)
#define FOPENOUT(OUT) freopen(OUT, "w", stdout) template<class T> T CMP_MIN(T a, T b) { return a < b; }
template<class T> T CMP_MAX(T a, T b) { return a > b; }
template<class T> T MAX(T a, T b) { return a > b ? a : b; }
template<class T> T MIN(T a, T b) { return a < b ? a : b; }
template<class T> T GCD(T a, T b) { return b ? GCD(b, a%b) : a; }
template<class T> T LCM(T a, T b) { return a / GCD(a,b) * b; } //typedef __int64 LL;
typedef long long LL;
const int MAXN = ;
const int MAXM = ;
const double eps = 1e-; struct NODE
{
int s, b;
NODE(){}
NODE(int _s,int _b)
{
s = _s;
b = _b;
}
bool operator < (const NODE& B)const{
return s > B.s;
}
};
int k, a[MAXN], b[MAXN]; void mergeArray()
{
priority_queue<NODE>q;
for(int i = ; i < k; i++)
{
q.push(NODE(a[i]+b[], ));
}
for(int i=;i<k;i++)
{
NODE top = q.top(); q.pop();
a[i] = top.s;
int id = top.b;
if(id+ < k) q.push(NODE(top.s+b[id+]-b[id], id+));
}
} int main()
{
// FOPENIN("in.txt");
// FOPENOUT("out.txt");
while(~scanf("%d", &k))
{
mem0(a);
for(int i=;i<k;i++) scanf("%d", &a[i]);
for(int i=;i<k;i++)
{
for(int j=;j<k;j++) scanf("%d", &b[j]);
sort(b,b+k);
mergeArray();
}
for(int i=;i<k;i++)
{
printf("%d%c", a[i],i==k-?'\n':' ');
}
}
return ;
}