UVA11997 K Smallest Sums 题解

题链接:uva 11997 K Smallest Sums

  • 题目大意

给定\(n\)个序列,各自包含有\(n\)个元素.在每个序列里各选一个元素加起来得到一个和,一共可以得到\(n^2\)个.求这些和中最小的\(n\)个的值(重复的值也可以重复计算).

数据范围:

\(1 \leq n \leq 750\)

数值和不会爆int

注意重复的值也是算进去的,不要求严格

  • 思路

由于这个问题看起来特别牛逼,不妨先考虑一个简单情况:假设\(n=1\)的话就是自己,不用考虑.如果\(n=2\)的时候就会有序列合并的操作了,假设解决了\(n=2\)的情形,那么接着可以想到对于原问题实际上就可以靠\(n=2\)的合并思路求解了,因为对于\(n\)个的情况,就相当于是两两合并到一个序列.

考虑怎么做\(n=2\)的情形,问题在于不可能把所有的可能性全部罗列出来,那就需要用到某种性质或者特殊方式使得能从某个前面的结果推出后面的结果,就是知道前面最小的,不说直接能知道下一个是谁,但是要知道可能有的候选的最小值有哪些加进去考虑.按这个思路,往下想的话,可以想到一个关键就是:确定某种顺序,使得整个问题一步一步可以递进找到候选的方案.

把两个序列分别记作A和B的话那么所有的值可以这样排列:

\(A_1 + B_1 \leq A_1 + B_2 \leq A_1 + B_3 \leq ... \leq A_1 + B_n\)

\(A_2 + B_2 \leq A_2 + B_2 \leq A_2 + B_3 \leq ... \leq A_2 + B_n\)

\(.....................................\)

\(A_n + B_1 \leq A_n + B_2 \leq A_n + B_3 \leq ... \leq A_n + B_n\)

显然最开始的时候,候选者只有两个:\(A_1 + B_1\)或者\(A_2 + B_1\)那么假设\(A_1+B_1\)是比较小的一方,也就是他被选走了,那么接下来第一个序列不能选第一个和了,只能选第二个,循此往复就可以选出\(n\)个最小值了.因为对于每个表来说都有一个递进的不等关系,并且选掉一个,下一个作为候选值不会出现跳跃的问题.那么剩下的思路也就比较显然了,对原来的\(n\)个序列的问题,每次取一个序列和第一个序列合并,并记录之前的答案,每次合并的时候建一个优先队列取最小值.注意这里算出来的答案是会对以后的结果有影响的,就是整个过程是从第二个序列开始一直从后面选一段序列和第一个合并,那么第二个序列和第一个序列合并之后的结果必须要覆盖掉第一个序列,不然会导致结果偏小,因为你再之后来合并的时候就不应该用原来的\(A_1\)序列合并了,而是已经合并好的上面的所有的序列.

最后提一下怎么处理优先队列的问题,只需要记录一个二元组\((s,b)\)其中\(s = A_a + B_b\),一开始把所有的第一列的元素加入候选项.那么某个二元组\((s,b)\)被选择了之后,下一个\((s',b+1)\)里的\(s' = A_a + B_{b+1}\),进一步可以表示成\(s'=s+B_{b+1}-B_b\),也就是说不需要知道原来的\(A_a\)是谁,只通过\(b\)就可以算出来了.细节问题可以参考代码.

  • 代码

    #define _CRT_SECURE_NO_WARNINGS
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1000;
    int A[N][N];
    int n;
    struct Node
    {
    	int s,b;
    	const bool operator<(const Node& o)	const
    	{
    		return s > o.s;
    	}
    };
    void merge(int* A,int* B,int* C)
    {
    	priority_queue<Node> pq;
    	for(int i = 1;i <= n;++i)	pq.push({A[i] + B[1],1});
    	for(int i = 1;i <= n;++i)
    	{
    		auto t = pq.top();pq.pop();
    		C[i] = t.s;
    		int b = t.b;
    		if(b + 1 <= n)	pq.push({C[i] + B[b + 1] - B[b],b + 1});
    	}
    }
    int main()
    {
    	while(scanf("%d",&n) == 1)
    	{
    		for(int i = 1;i <= n;++i)
    		{
    			for(int j = 1;j <= n;++j)
    				scanf("%d",&A[i][j]);
    			sort(A[i] + 1,A[i] + n + 1);
    		}
    		for(int i = 2;i <= n;++i)
    			merge(A[1],A[i],A[1]);
    		for(int i = 1;i <= n;++i)	printf("%d%c",A[1][i],i == n ? '\n' : ' ');
    	}
        return 0;
    }
    
    
posted @ 2020-08-14 10:42  随处可见的阿宅  阅读(119)  评论(0编辑  收藏  举报