UPC-2654 序列合并(广搜&优先队列)

题目描述
有两个长度都是N的序列A和B,在A和B中各取一个数相加可以得到N^2个和,求这N^2个和中最小的N个。

输入
第一行一个正整数N;

第二行N个整数Ai,满足Ai<=Ai+1且Ai<=10^9;

第三行N个整数Bi, 满足Bi<=Bi+1且Bi<=10^9.

输出
仅一行,包含N个整数,从小到大输出这N个最小的和,相邻数字之间用空格隔开。

样例输入
3
2 6 6
1 4 8

样例输出
3 6 7

提示
对于50%的数据中,满足1<=N<=1000;
对于100%的数据中,满足1<=N<=100000。

一度觉得被这道题逼得走投无路了,一眼看过去总以为,因为两个序列都是非非严格递增的,所以要求 和 最小就直接拿两个序列中的第一个值与剩下的所有值求和取前n小即可。但是这并不严谨。如样例:
5
7 8 1000 1002 1003
2 22 40 63 80
很明显结果由8+22是应该输出的,但计算过程中并没有算到这种情况。

于是开始考虑更加深层的情况。观察序列中首先有个特点是非严格递增的,那么值是越来越大的。并且,为什么之前没有取到8与22的求和情况?因为认为最小值加上剩余的值会是和最小的情况,但遇到的样例如果在其中一个序列中出现了猛的递增差,那么拿第一个求和,加上了这个过大的递增差,不如拿第二第三第四个与前几个递增差小的数求和得到数更小。
于是我们计算序列中两两数字的递增差

7—(1)–8—(992)–1000—(2)— 1002—(1)— 1003

2—(20)— 22—(18)– 40 —(23)– 63—-(17)— 80

可以发现,我们最终取的结果是 9 10 29 30 47
即2+7=9
2+7+1=10
2+7+20=29
2+7+1+20=30
2+7+20+18=47【为什么没有取1?因为取1意味着上方序列取的值是8,否则就是7,应该拿更小的7+40】

因为我们每次加的是差值,所以,可以按照差值的顺序求和,从而依次得到序列中的每一个数,利用差值得到的新的数,可以继续拿差值求和得到两序列任意两数之和,并且这个和一定是序列中存在的数求得的和。

那么问题就很清楚了,我们在取差值求和的过程中没有取到较大的递增差992,而是一直取较小的递增差进行新数的构造,新数的求和。完美绕过了过大的差值求和。那么一直取最小差值求和,直到求出n个值,那么这n个值就是前n个最小和。

对于一个差值,我们无法判断其是否足够的大,以至于让我绕过去不取他,因此加上992的操作还是会有的,但是,在求和后的值相对比较过程中,因为他很大,比后来的求和都大,那就被无限搁置了,也就是取不到这个超大和。

利用优先队列,一直取求和的较小值,那较小值再按差值位序求和得到更多的新值【也就是序列中后面的值】,可以筛选出前n个较小的求和。按照差值位序是因为这是个按序递增的序列,如果不按序,那么递增差的屏障作用就无法体现,递增差的落差阻碍了后面的值加入到求和的候选值中,因为他们在落差后面,他们的值更大,即使他们之间的差值可能很小。

这样的差值形成的意义是什么?递增差的前缀和即整个原序列的每一个值

利用这样的规律广搜,得到n个值后退出搜索。即前n个最小值。

搜索的起点是一定是最小求和的两序列中的第一个数之和。并且,因为依次取差值的方法,可能以不同的先后顺序遍历到相同的差值,如30可以由2+7+1和2+7+20两数的下一步操作同时得到,这样30就重复了,于是用map标记使用差值位序的情况。如果遇到了两次加第二个差值,那么这个答案应该是被忽略的。

好像看很多人利用二分或者优先队列筛选弹出较大求和值的方法过的,这个太麻烦的方法也是自己无可奈何而不得已做出的,比较复杂。仅供讨论。

#include<bits/stdc++.h>
#define LL long long
#define M(a,b) memset(a,b,sizeof a)
using namespace std;
const int maxn=1e5+7;
LL n,a[maxn],b[maxn],disa[maxn],disb[maxn];
struct node
{
    int sum,i,j;
    node(){}
    node (int a,int b,int c)
    {
        sum=a;
        i=b;
        j=c;
    }
    bool operator <(const node &a)const
    {
        return sum>a.sum;
    }
};
priority_queue<node>q;
map<LL,bool>vis;
int main()
{
    scanf("%d",&n);
    for(int i=0; i<n; i++)
    {
        scanf("%d",&a[i]);
        if(i!=0)disa[i]=a[i]-a[i-1];///计算序列相邻数差值
    }
    for(int i=0; i<n; i++)
    {
        scanf("%d",&b[i]);
        if(i!=0)disb[i]=b[i]-b[i-1];
    }
    while(!q.empty())q.pop();
    vis.clear();
    int cnt=0;
    q.push(node(a[0]+b[0],0,0));///a1和bi作为起点最小值开始搜索
    while(!q.empty())
    {
        node tmp=q.top();
        q.pop();
        cnt++;
        printf("%d%c",tmp.sum,cnt==n?'\n':' ');///取出一个最小值输出
        if(cnt==n)break;///将最小值加上两序列中的下一个差值送入优先队列中
        if(!vis[(tmp.i+1)*1000000+tmp.j])q.push(node(tmp.sum+disa[tmp.i+1],tmp.i+1,tmp.j)),vis[(tmp.i+1)*1000000+tmp.j]=true;
        if(!vis[tmp.i*1000000+tmp.j+1])q.push(node(tmp.sum+disb[tmp.j+1],tmp.i,tmp.j+1)),vis[tmp.i*1000000+tmp.j+1]=true;
    }
    printf("\n");
}
/*
5
7 8 1000 1002 1003
2 22 40 63 80

3
2 6 6
1 4 8

9 10 29 30 47

*/
posted @ 2018-08-10 22:52  KuroNekonano  阅读(189)  评论(0编辑  收藏  举报