zoukankan      html  css  js  c++  java
  • UVA 11997 K Smallest Sums 优先队列+归并 STL

      题目链接: UVA......

      题目描述: 有K组数, 每组数有K个, 所以每组数各选一个加和有k^k种情况, 要求输出其中的最小的前k种, 从小到大输出

      解题思路: 首先对于两个数组取前K个, 构造二元组(s, b) 其中s = Aa + Bb , a, b 为下标。 为什么不用三元组(s,a,b)呢, 因为二元组完全可以表示三元组, 下一个元素就是s+B[i+1]-B[i] .

          

            我们需要把这k^2个和组织成如下k个有序表.(A, B是有序的哦)

            表1:A1+B1<=A1+B2<=......<=A1+Bk

            表2:  A2+B1<=A2+B2<=......<=A2+Bk

            表k:Ak+B1<=AK+B2<=......<=Ak+Bk

              再说这种构造方法为什么可行, 这种构造方法保证了当最小的出队之后, 下一个推进的一定是最优的, 因为如上图所示, 对于每组数, 相邻两项相差的都是B[i]-B[i-1], 而推出的机制又保证了数值的最小, 所以与推出的数据相差最小的一定是推出数据的后一个元素

            最后再两两归并算出最终结果, 复杂度是n^2logn(排序)

      代码: 

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    //priority_queue<int> heap;
    //priority_queue<int, vector<int>, greater<int>() > mm;
    struct Item {
        int s, b; // s = Aa + Bb 所以用下标b和sum二元组表示 k个数组中的元素
        bool operator < ( const Item & r ) const {
            return s > r.s;
        }
    };
    const int MAXN = 800;
    int a[MAXN][MAXN];
    int k;
    
    void merge( int * A, int * B, int * C ) { // A,B是待处理数组,C是结果数组
        int cnt = 0;
        priority_queue<Item> pq;
        for( int i = 0; i < k; i++ ) {
            pq.push((Item){A[i]+B[0], 0}); // 将新建的k个数组中的第一个推进队列, 最小的肯定在其中
        }
        while(!pq.empty()) {
            Item t = pq.top(); // 通过最小堆得到最小值
            pq.pop();
            C[cnt++] = t.s;
            if( t.b + 1 < k ) {
                pq.push((Item){t.s + (B[t.b+1]-B[t.b]), t.b + 1}); // 推进弹出队列的数组中下一个元素(为什么可行题解中会解释)
            }
            if( cnt == k ) break;
        }
    }
    
    int main() {
        while( ~scanf( "%d", &k ) ) {
            memset(a, 0, sizeof(a));
            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-1; i++ ) {
                merge(a[0], a[i], a[0]);
            }
            for( int i = 0; i < k; i++ ) {
                if( i == 0 ) {
                    printf( "%d", a[0][i]);
                }
                else {
                    printf( " %d", a[0][i]);
                }
            }
            printf( "\n" );
        }
        return 0;
    }
    View Code

      思考: 一开始我用了错误的思路, 还以为很对......就是找出每组最小的数, 再找出次小的数推入队列, 这样不能保证是最小的, 因为如果有两个分别大一, 而有一个大三,这个算法会选择大三的......所以这道题难点就是如何构造这个优先队列, 一定要能够保证推出的数的下一个数是最小的。看来以后做题不能直接想当然的上手去写, 写之前多多思考, 仔细想想自己的算法是否能够满足自己想达到的目的, 再去动笔写

  • 相关阅读:
    ubuntu国内镜像源
    windows安装Pygraphviz
    python dict与collections.defaultdict的区别
    python生成 requirements.txt文件
    python list 和 dict前加星号
    Ubuntu安装Docker
    Zookeeper核心概念及读写流程
    docker安装mysql5和mysql8
    ubuntu docker更改默认镜像和容器存储位置
    训练篇-胸
  • 原文地址:https://www.cnblogs.com/FriskyPuppy/p/7243177.html
Copyright © 2011-2022 走看看