思路题,多路归并(K Smallest Sums,UVA 11997)

来源:互联网 发布:在家网络兼职招聘 编辑:程序博客网 时间:2024/06/06 03:34

最简单的多路归并就是归并排序,只需要将两个有序表合成为1个有序表即可。首先表只有2个,其次限制条件也只有挑一个最小的。所以非常简单。实现方法往往是弄两个指针,然后看哪个指针指向的值小,就把哪个值放进来,然后指针++。


但是有时我们会遇到更为复杂的问题,比如说有多个有序表,而且限制条件又比较复杂的情况,有时甚至连我们该归并什么,有序表里该放什么都不知道。


这时我们就最好要有一些多路归并的经验了。


大白书P189上写的一些话很值得仔细看懂,它揭露了前面那道水题——例题3的本质。


“推而广之,我们实际上已经学会解决多路归并问题,即把k个有序表合并成一个有序表(假定每个表都是升序排序)——用优先队列维护每个表的“当前元素


注意下,一个数组不等于一个有序表。

一个有序表就是一路。


而例题4就需要更加灵活的思路了——题目说k个数组中,每个数组挑且只挑一个数,然后全部加起来,最后求k个最优值。那我们不妨一个数组一个数组地加进来,然后一加一边维护k个最优值就好了。这样就只需要考虑如何解决两个数组相加的问题了。


题目限制为每个数组里都要挑一个,然后求和。只考虑两个数组的话,那就是这两个数组里各挑一个,然后相加。

那么有序表是什么呢?一个表内的元素一定是一个跟着一个的,前面的走了,后面的才能来,即每次只能出一个代表。表内的元素是可以互相替代的,只不过要挑一个最优的出来,表间的元素是不可相互替代的,而且他们并起来一定是全集。

那就先找不可相互替代的咯。

很快就发现,一个数组内的数都是不可相互替代的(能且只能取一个),因此他们各自成一个表。每个数都可以任意搭配另一个数组内的任意一个数,那么对于某个数,它的所有搭配都是可以相互替代的(因为对于某个数,它的所有搭配都可以代表这个数),所以在一个表内。最终保证了优先队列里始终没有重复和遗漏的元素。

然后就“用优先队列维护每个表的“当前元素””就好了。


反正就是两个数组的话,先按一个数组分类,然后用另一个数组搭配。

分类分得不重不漏,每类都包含所有可能的搭配,每类出一个最优的搭配,再在其中挑一个最优的。保证是最优解,然后把这个最优解删去,即“当前元素”变成了“次选”,然后不断重复。优点是只用动态地保存前k个最优解,不需要枚举所有情况。挑选以及删除的过程有点像堆。嗯,优先队列是个二叉的堆,而这个是多叉的堆。


代码

#include<bits/stdc++.h>#define f(i) for(int i=0;i<n;i++)#define s(a) scanf("%d",&a)#define ss(A,i) scanf("%d",A+i)using namespace std;typedef pair<int,int> pii;int n;int A[800];int B[800];void mg(int*A,int*B,int*C){    priority_queue<pii,vector<pii>,greater<pii> >q;    f(i) q.push(make_pair(A[i]+B[0],0));    f(i)    {        C[i]=q.top().first;        q.push(make_pair(q.top().first-B[q.top().second]+B[q.top().second+1],q.top().second+1));        q.pop();    }}int main(){    while(~s(n))    {        f(i) ss(A,i);        sort(A,A+n);        int num=n-1;        while(num--)        {            f(i) ss(B,i);            sort(B,B+n);            mg(A,B,A);        }        f(i) printf("%d%c",A[i],i==n-1?'\n':' ');    }    return 0;}


0 0
原创粉丝点击