AcWing 序列

AcWing 序列

Description

  • 给定m个序列,每个包含n个非负整数。

    现在我们可以从每个序列中选择一个数字以形成具有m个整数的序列。

    很明显,我们一共可以得到n^m个这种序列, 然后我们可以计算每个序列中的数字之和,并得到n^m个值。 现在请你求出这些序列和之中最小的n个值。

Input

  • 第一行输入一个整数T,代表输入中包含测试用例的数量。

    接下来输入T组测试用例。

    对于每组测试用例,第一行输入两个整数m和n。

    接下在m行输入m个整数序列,数列中的整数均不超过10000。

Output

  • 对于每组测试用例,均以递增顺序输出最小的n个序列和,数值之间用空格隔开。

    每组输出占一行。

Data Size

  • 0<m≤1000,
    0<n≤2000

Sample Input

1
2 3
1 2 3
2 2 3

Sample Output

3 3 4

题解:

  • 二叉堆。
  • 妙题,妙不可言。
  • lyd老师讲得太好了,我就直接当一回搬运工:D!
  • 先来考虑M = 2的简化问题,即从2个序列中任取一个数相加构成的N ^ 2个和中求出前N小的和。设这两个序列为A和B,把它们分别排序。
  • 可以发现,最小和一定是A[1] + B[1],次小和是min(A[1] + B[2], A[2] + B[1])。假设次小和是A[2] + B[1],那么第3小和就是A[1] + B[2],A[2] + B[2], A[3] + B[1]的三者之一。也就是说,当确定A[i] + B[j]为第k小和时,A[i + 1] + B[j]和A[i] + B[j + 1],就加入了第k + 1小和的备选答案中。
  • 需要注意的是,A[1] + B[2]和A[2] + B[1]都可以产生A[2] + B[2]这个备选答案。为了避免重复,我们可以规定:如果把j + 1加入备选答案,那么以后就只能再增加j,不能增加i。
  • 所以,我们建立一个小根堆,堆中每个节点存储一个三元组(i, j, last),其中last表示上一次移动的指针是不是j。起初,堆中只有(1, 1, false)。
  • 那么每一步取出堆顶(i, j, last),然后把(i, j + 1, true)插入堆,如果last为false,再把(i + 1, j, false)插入堆。重复N次,每次取出堆顶一起构成前N小和。
  • 回到本题,根据数学归纳法,先求出前2个序列的前N小和作为新的序列,然后再将新序列与后面的序列合并,最终得到答案。
  • 总结:有时思考问题要将问题缩放,即考虑最小规模的数据,看看能不能类比,逐步推出问题规模的数据。(像这一题,有一种逐渐浓缩、缩点的味道)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#define N 1005
#define M 2005
using namespace std;

struct Node
{
    int i, j, val, las;
    friend bool operator < (Node x, Node y) {
        return x.val > y.val;
    }
};
int T, n, m;
int a[M], b[M], c[M];

int read()
{
    int x = 0, f = 1; char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
    return x *= f;
}

int main()
{
    cin >> T;
    while(T--)
    {
        n = read(), m = read();
        for(int i = 1; i <= m; i++) a[i] = read();
        sort(a + 1, a + 1 + m);
        for(int i = 2; i <= n; i++)
        {
            for(int j = 1; j <= m; j++) b[j] = read();
            sort(a + 1, a + 1 + m), sort(b + 1, b + 1 + m);
            priority_queue<Node> que;
            que.push((Node){1, 1, a[1] + b[1], 0});
            c[1] = a[1] + b[1];
            for(int j = 2; j <= m; j++)
            {
                Node now = que.top(); que.pop();
                que.push((Node){now.i, now.j + 1, a[now.i] + b[now.j + 1], 1});
                if(!now.las) que.push((Node){now.i + 1, now.j, a[now.i + 1] + b[now.j], 0});
                c[j] = que.top().val;
            }
            for(int j = 1; j <= m; j++) a[j] = c[j];
        }
        for(int j = 1; j <= m; j++) printf("%d ", a[j]);
        printf("\n");
    }
    return 0;
}

转载于:https://www.cnblogs.com/BigYellowDog/p/11352201.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值