UVA 11997 K Smallest Sums(优先队列)

UVA 11997 K Smallest Sums(优先队列)

题意:

        给你一个整数K,并且给你K组数,每组K个数,现在在每组中任取一个数,然后相加可以得到一个"和",这样的和共有K^K个.要你输出所有可能和值中最小的那K个。

分析:

        刘汝佳:训练指南P189例题.

        问题1:如果只有A,B,C三个大小为K的数组,我们如何求"和"能获得最小的前K个和呢?

        我们只需要将A和B数组求出前K小的和(第K+1小到之后的所有和值我们都不用管,因为后面压根用到这些值),然后用这个和数组去同样与C数组求出前K小的和即可。如果有K个这样的数组,我们依然用A1与A2求前K小的和,然后用和数组与A3再求和,然后用结果再继续同样与A4求和,与A5求和...等即可。

        问题2:A和B两个数组求前K小的和,如何计算能快速得到解呢?

        穷举法要K^2的复杂度.这里我们用优先队列来解.因为原本有K^2个可能的和结果。

        假设A与B中的数已经排好了序(从小到大),那么前K小的数肯定从下面K个队列中出来(我们等同于将K^2个和分成了K个队列,且每个队列中的元素都是从小到大排列):

        队列1: A[1]+B[1] , A[1]+B[2] , … , A[1]+B[K]

        队列2: A[2]+B[1] , A[2]+B[2] , … , A[2]+B[K]

        队列3: A[3]+B[1] , A[3]+B[2] , … , A[3]+B[K]

        队列4: A[4]+B[1] , A[4]+B[2] , … , A[4]+B[K]

        ...

        队列4: A[K]+B[1] , A[K]+B[2] , … , A[K]+B[K]

        且每个队列中队首元素肯定是当前最小的候选和之一.所以我们每次从上述K个队列中的K个队首元素里找出最小的那个作为一个前K小的和(每次操作复杂度为logK),连续K次操作可以得到所有前K小的和。

        (注意:程序实现只用了一个优先队列priority_queue,此队列中保存了上述K个队列的K个对首元素)

        所以我们只需要K^logK复杂度即可求出2个数组组合的前K小数.

        综上所述,我们只需要一次构建两个数组的前K小数即可求得最小的前K小数了.

AC代码(新):

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;

const int maxn = 750+5;
int A[maxn][maxn];

//Node用于构建优先队列的元素
struct Node
{
    int sum;//和
    int id;//B数组元素下标
    Node(int sum,int id):sum(sum),id(id){}

    bool operator<(const Node &rhs)const
    {
        return sum>rhs.sum;
    }
};

//将A与B数组的前n小和存入C数组中
void merge(int *A,int *B,int *C,int n)
{
    priority_queue<Node> Q;
    for(int i=0;i<n;i++)
        Q.push(Node(A[i]+B[0],0));

    for(int i=0;i<n;i++)
    {
        Node tmp=Q.top(); Q.pop();
        C[i]=tmp.sum;
        if(tmp.id+1<n) Q.push(Node(tmp.sum-B[tmp.id]+B[tmp.id+1], tmp.id+1));
    }
}

int main()
{
    int k;
    while(scanf("%d",&k)==1)
    {
        for(int i=0;i<k;i++)
        {
            for(int j=0;j<k;j++)
                scanf("%d",&A[i][j]);
            sort(A[i],A[i]+k);
        }

        for(int i=1;i<k;i++)
            merge(A[0],A[i],A[0],k);

        printf("%d",A[0][0]);
        for(int i=1;i<k;i++)
            printf(" %d",A[0][i]);
        printf("\n");
    }
    return 0;
}

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1000;
int a[maxn],b[maxn];
int k;
struct node
{
    int va;//A数组的值
    int id;//B数组的元素序号
    bool operator<(const node&rs)const
    {
        return va+b[id]>rs.va+b[rs.id];
    }
};
void merge(int *a,int *b,int *c)
{
    priority_queue<node> pq;
    for(int i=0;i<k;i++)
    {
        node r;
        r.va=a[i];
        r.id=0;
        pq.push(r);
    }
    for(int i=0;i<k;i++)
    {
        node r=pq.top();pq.pop();
        c[i]=r.va+b[r.id];
        if(r.id<k-1)
        {
            r.id++;
            pq.push(r);
        }
    }
}
int main()
{
    while(scanf("%d",&k)==1)
    {
        for(int i=0;i<k;i++)
            scanf("%d",&a[i]);
        sort(a,a+k);
        for(int i=1;i<k;i++)
        {
            for(int j=0;j<k;j++)
                scanf("%d",&b[j]);
            sort(b,b+k);
            merge(a,b,a);
        }
        printf("%d",a[0]);
        for(int i=1;i<k;i++)
            printf(" %d",a[i]);
        printf("\n");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值