【BIT2021程设】16.集合——中位数机、对顶堆

这篇博客介绍了作者在十一假期期间对小学期程序设计作业的总结,主要针对寻找使所有同学曼哈顿距离之和最小的集合点问题。通过分析得出,该问题可以通过维护大根堆和小根堆动态计算中位数来解决。文章提供了伪代码,并强调了在处理浮点数时避免不必要的计算,以防止超时。博客还包含了完整的C++代码实现。
摘要由CSDN通过智能技术生成

写在前面:

本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。

不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。

同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。

加油!


 类似题指路:力扣O41

成绩10开启时间2021年09月3日 星期五 11:30
折扣0.8折扣时间2021年09月7日 星期二 23:00
允许迟交关闭时间2021年10月10日 星期日 23:00

Description

想必同学们已经深刻体会到军训的辛苦了,与此同时我们也应该感谢教官们的辛苦付出。现在请你帮助教官小张解决如下问题吧!

马上集合了,但现在同学们分布在操场各个位置。共有n位同学,他们在操场的位置可以用(x_i,y_i)表示。现在小张想知道对于每一个i,前i个同学集合应该如何选择位置使得所有同学走的总曼哈顿距离最短。

Input

本题为多组数据。

第一行一个数,T表示数据组数。

每组数据第一行一个数n(1 \leq n \leq 500000 ),表示同学个数。

接下来n行,每行两个数x_i,y_i(-10^9 \leq x_i,y_i \leq 10^9 ),表示每个同学位置。

数据保证所有组数据\sum n \leq 500000

Output

每组数据第一行输出数据组数的信息(见样例)。

之后n行,每行一个数,保留4位小数,表示前i位同学集合到同一个点最少走的曼哈顿距离。

Notes

(x_1,y_1),(x_2,y_2)两点曼哈顿距离为|x_1-x_2| +|y_1-y_2|

测试用例 1以文本方式显示
  1. 1↵
  2. 5↵
  3. 1 1↵
  4. 1 1↵
  5. 1 -1↵
  6. -1 1↵
  7. -1 -1↵
以文本方式显示
  1. Case: 1↵
  2. 0.0000↵
  3. 0.0000↵
  4. 2.0000↵
  5. 4.0000↵
  6. 8.0000↵
1秒64M

题意分析:

        找最小曼哈顿距离之和的集合点。曼哈顿距离不再解释, 题目里就有。

        这题我们首先可以注意到,曼哈顿距离的x和y的计算应该是互相独立、互不干扰的,所以我们可以把两者单独拉出来算。那么问题就被简化为了一维的:在数轴上寻找一个点,使得若干个点与其的距离之和最小,实际上这个距离之和就是这样一个函数:

        f(x)=\left | x-a_1 \right |+\left | x-a_2 \right |+\left | x-a_3 \right |+\begin{matrix} &...& \end{matrix}+\left | x-a_n \right |

        高中数学好的同学其实一眼就知道这是什么玩意了,要是还是看不出来呢,那我画个图给你看。

         以上两幅图分别是{a_n}为{1,2,4,7,11}和{1,2,3,4,7,11}的情况。

        用数学方法不难分析出,当总元素个数为奇数时,f(x)的最小值点为所有元素的中位数,最小值为中位数与其他元素之差的和;当总元素个数为偶数时,f(x)的最小值点取遍区间[a,b],其中a,b是所有元素中间的两个,最小值为a,b任一个与其他所有元素(包括没选上的那一个a或者b)之差的和。

        这题既然要求我们对于每输入一个数据,就更新一个输出,那我们就需要一个能动态取出中位数的容器,并且取中位数的时间得控制在常数级别。那要怎么做呢?

        我们回想一下,如果我们需要动态存取一个容器中最大的那个值,我们会用什么?答案是堆,也就是优先队列(名词解释:堆与优先队列)。同时,想要动态存取最小值,也可以用优先队列。那动态存取中位数该怎么办呢?

        聪明的人已经想到了,最大的那一半,其中的最小值,或者最小的那一半其中的最大值,不就是我们要的中位数吗(二者至少其中之一是)!所以我们的目标就是,构建一个大根堆,用于保存较小的那一半;构建一个小根堆,用于保存较大的那一半;同时既然是“各一半”,我们就要尽量保证两个根堆大小相等——最多差一个。

        于是,整个思路就非常的自然了。

        详情看伪代码。


伪代码:

        这里只写中位数机的伪代码,写出中位数机以后后续的步骤就不难了。

        构建一个大根堆maxHeap,一个小根堆minHeap

        //整个过程中,我们保证大根堆的大小至多比小根堆大1,当然,反过来定义也行,看你喜欢

        读入新数据的操作:

                如果大根堆和小根堆一样大,将数据先压入小根堆,同时把小根堆堆顶弹出,压入大根堆;

                //想一想,为什么要这么做?——目的是保证小根堆存放的皆为“较大的”那一半,大根堆存放“较小的那一半”,如果直接压入大根堆,并不能保证新数据属于较小的那一半,于是先压入小根堆,再把小根堆最小的那个取出还给大根堆,不用担心压入小根堆的那个数据过小导致破坏这个过程——如果过小,那之后会马上被弹出

                如果大根堆更大,先将数据压入大根堆,再将大根堆堆顶弹出,压入小根堆;

        取中位数的操作:

        //我们不是严格取中位数,当中间有两个数时,我们任取其一就行,如果要严格取中位数,简单改造就行,留作读者自行思考

                无论两个堆大小关系如何,都返回大根堆顶;

                //当然,你要分类讨论也行,大根堆大时返回大根堆顶,一样大时返回二者的平均数,然而这样对这题来说反而不好,理由一会说


注意事项:

        为什么说“正经地返回中位数”不好呢?这不是鼓励我们投机取巧吗?

        实际上,如果我们正经地返回中位数,有可能会涉及到浮点数的计算(比如中间数字是3和4),而double类型的计算相较于long long是非常之慢的,超时就在一念间。既然这题全部输入都是整型,并且我们也证明了我们所需要的点就是所有点其中之一,也是一个整型,那自然是能不引入浮点就不引入浮点。

        然而,题目最终要求我们输出浮点数,那咋办啊?

        实际上,你会发现,不论怎么算,最后输出出来的所谓“浮点数”,都没有小数部分(因为我们选定的集合点是一个整数,那接下来必定只有整数的加减法,结果自然不会有小数部分),我认为这是题目设计的疏漏。(当然,到了你们这一届可能题目已经改了)

        其他的注意事项就比如,要开long long这样的老生常谈了,不再赘述。

贴代码:

        

    #include <bits/stdc++.h>  
    using namespace std;  
    typedef long long ll;  
    const int INF = 0x3f3f3f;  
    const double EPS = 1e-8;  
    #define __MAX 100010  
            
    class MedianFinder{  //中位数机
    private:  
        priority_queue<ll, vector<ll>, less<ll>> maxHeap;  
        priority_queue<ll, vector<ll>, greater<ll>> minHeap;  
    public:  
        MedianFinder(){}  
        void addNum(ll num){  
            if(maxHeap.size() == minHeap.size()){  
                minHeap.push(num);  
                ll val = minHeap.top();  
                minHeap.pop();  
                maxHeap.push(val);  
            } else{  
                maxHeap.push(num);  
                ll val = maxHeap.top();  
                maxHeap.pop();  
                minHeap.push(val);  
            }  
        }  
        ll getMedian(){  
            if(maxHeap.size() == minHeap.size()){  
                return ((maxHeap.top() + minHeap.top()) / 2);  
            } else{  
                return (maxHeap.top());  
            }  
        }  
        ~MedianFinder(){}  
    };  
            
    int main(){  
        ///ifstream infile("input.txt", ios::in);  
        ///ofstream outfile("output.txt", ios::out);  
            
        int t;  
        cin >> t;  
        int n;  
        ll tx, ty;  
        for(int i = 1; i <= t; i++){  
            MedianFinder x;  
            MedianFinder y;  
            scanf("%d",&n);  
            printf("Case: %d\n",i);  
            ll cur = 0;  
            int mx;  
            int my;  
            for(int j = 0; j < n; j++){  
                scanf("%lld %lld",&tx, &ty);  
                if(j == 0){  
                    mx = tx;  
                    my = ty;  
                }  
                x.addNum(tx);  
                y.addNum(ty);  
                if(j % 2 == 0){  
                    mx = x.getMedian();  
                    my = y.getMedian();  
                    cur += abs(mx - tx) + abs(my - ty);  
                } else{  
                    cur += abs(mx - tx) + abs(my - ty);  
                    mx = x.getMedian();  
                    my = y.getMedian();  
                }  
                printf("%lld.0000\n",cur);  //假装输出浮点数
            }  
        }  
            
        return 0;  
    }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里之码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值