【CodeVS】1245 最小的N个和 - Ⅰ - 原题的几种解法

来源:互联网 发布:火影忍者手游开挂软件 编辑:程序博客网 时间:2024/05/07 12:22

【题意】有两个长度为N的序列AB,在AB中各任取一个数可以得到N2个和,求这N2个和中最小的N个。

【数据范围】Ai,Bi109N105

【思路1】30分做法
直接把N2个和都算出来,排序,然后输出前N个。
时间复杂度:O(n2logn)
空间复杂度:O(n2)

【思路2】AC做法
求前N小,涉及到单调性,试着排序使得A,B两个序列从小到大。

我们从1到N依次找到前N小的和,那么每次就要从决策中选出一个最小的元素,现在要求每次决策考虑的元素个数尽可能少。

可以按行把所有的N2个和分成N类:
第1行:A1+B1<A1+B2<A1+Bi<...<A1+Bn
第2行:A2+B1<A2+B2<A2+Bi<...<A2+Bn
......
第i行:Ai+B1<Ai+B2<Ai+Bi<...<Ai+Bn
......
第n行:An+B1<An+B2<An+Bi<...<An+Bn

对于第i行,若j<k,则必然先选Ai+Bj,后选Ai+Bk
那么,在决策的时候只用考虑每一行当前未选的最前一个。

即:设level[i]表示当前第i行的(Ai+Blevel[i])在决策中,每次选取s,满足最小化(Ai+Blevel[i]),然后把(As+Blevel[s])最为当前最小,再inc(level[s])
至于每次的决策,用堆可以做到O(logn)完成。

时间复杂度:O(nlogn)
空间复杂度:O(n)

代码:实测136ms

#include <cstdio>#include <cctype>#include <queue>#include <algorithm>using namespace std;const int N=161240;int n;int A[N],B[N];int lev[N];inline int Read(void){    int s=0,f=1; char c=getchar();    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;    for (;isdigit(c);c=getchar()) s=s*10+c-'0';    return s; }struct cmp{    inline int operator () (int i,int j)    {        return A[i]+B[lev[i]]>A[j]+B[lev[j]];    }};priority_queue<int,vector<int>,cmp> q;int main(void){    n=Read();    for (int i=1;i<=n;i++) A[i]=Read();    for (int i=1;i<=n;i++) B[i]=Read();    sort(A+1,A+n+1);    sort(B+1,B+n+1);    fill(lev+1,lev+n+1,1);    for (int i=1;i<=n;i++) q.push(i);    int now;    for (int i=1;i<=n;i++)    {        now=q.top(),q.pop();        printf("%d ",A[now]+B[lev[now]++]);        q.push(now);    }    printf("\n");    return 0;}

【思路3】AC做法
首先排序,然后还是用【思路2】的思路,从1到N依次求前N小。

注意到【思路2】中我们只使用了横向的分类,而没有考虑纵向的分类,这是可以有一些决策状态排除掉的。
于是我们想到直接把这些状态当作一个二维的图来看:

B\A 1 2 … i … n 1 2 … i … n

其中(i,j)这个格子表示Ai+Bj

我们变为了二维的扩展,即在当前状态中选择一个最小的,然后向下、向右扩展决策状态。
这里需要注意的是,有些格子是不用加入待决策状态中的:它的左边或上边还没有决策出来。
这样有一定的优化作用。

时间复杂度:O(nlogn)
空间复杂度:O(n)

代码:实测88ms

#include <cstdio>#include <cctype>#include <queue>#include <algorithm>using namespace std;#define mp(i,j) make_pair(i,j)#define fs first#define sc secondtypedef pair<int,int> PairInt;const int N=161240;int n;int A[N],B[N];int cross[N],line[N];inline int Read(void){    int s=0,f=1; char c=getchar();    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;    for (;isdigit(c);c=getchar()) s=s*10+c-'0';    return s; }struct cmp{    inline int operator () (PairInt Pi,PairInt Pj)    {        return A[Pi.fs]+B[Pi.sc]>A[Pj.fs]+B[Pj.sc];    }};priority_queue<PairInt,vector<PairInt>,cmp> q;inline int Check(int i,int j){    if (cross[i]+1<j) return 0;    if (line[j]+1<i) return 0;    return 1;}int main(void){    n=Read();    for (int i=1;i<=n;i++) A[i]=Read();    for (int i=1;i<=n;i++) B[i]=Read();    sort(A+1,A+n+1);    sort(B+1,B+n+1);    PairInt now;    q.push(mp(1,1));    for (int i=1;i<=n;i++)    {        now=q.top(),q.pop();        printf("%d ",A[now.fs]+B[now.sc]);        cross[now.fs]=now.sc;        line[now.sc]=now.fs;        if (Check(now.fs+1,now.sc)) q.push(mp(now.fs+1,now.sc));        if (Check(now.fs,now.sc+1)) q.push(mp(now.fs,now.sc+1));    }    printf("\n");    return 0;}

【思路4】AC做法
按照【思路1】,我们把所有可能的情况都算出来,然后排序。
然而【思路1】的时间和空间都是我们承受不了的,怎么办?
想办法把一些一定不可能的状态给消除掉。

首先还是给A,B排序,同样还是这个表:

B\A 1 2 … i … n 1 2 … i … n

观察到,对于(i,j)这个点,比它小的元素至少有i×j1个。
由于我们要求前N小的,所以满足要求的点至少要满足i×j1<ni×jn
这样我们可以把点的个数缩小至

n1+n2+...+ni+...+nn=O(ni=1n1i)=O(nlogn)

时间复杂度:O(nlog2n)
空间复杂度:O(nlogn)

代码:实测172ms

#include <cstdio>#include <cctype>#include <algorithm>using namespace std;const int N=161240;const int S=3000000;int n;int A[N],B[N];int t[S],len;inline int Read(void){    int s=0,f=1; char c=getchar();    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;    for (;isdigit(c);c=getchar()) s=s*10+c-'0';    return s; }int main(void){    n=Read();    for (int i=1;i<=n;i++) A[i]=Read();    for (int i=1;i<=n;i++) B[i]=Read();    sort(A+1,A+n+1);    sort(B+1,B+n+1);    for (int i=1;i<=n;i++)        for (int j=1;i*j<=n;j++) t[++len]=A[i]+B[j];    sort(t+1,t+len+1);    for (int i=1;i<=n;i++) printf("%d ",t[i]);    printf("\n");    return 0;}

【小结】

  1. 对于无序的集合,通常要将它定序,常见的定序方法就是从小到大或者从大到小。

  2. 求第K小的方法通常有以下几种:
    ①依次求
    ②排序可能情况
    ③二分答案

  3. 对于方法①“依次求”,每次在待定状态内的元素要尽可能少,可以通过某些性质来减少元素的个数。
    通常的做法是构建多条元素的单调序列,满足先选完前一个再选后一个,这样用优先队列甚至不用(例如《丑数》一题)即可。
    这种做法甚至可以扩展到二维单调性,然后在平面上扩展。

  4. 对于方法②“排序可能情况”,待定情况要尽可能的少,这要通过某些性质来排除一些不可能的情况。
    例如本题只限定在i×jn内求,最后弄出了调和级数,总共的情况数为O(nlogn)

  5. 对于方法③,在【变式】会提及。

  6. 方法的比较
    方法①,方法②处理范围较小,询问较多的问题;
    方法③处理范围较大,询问较少的问题。

2 0
原创粉丝点击