题目描述:给出平面上n个点,找一个矩形,使得边界上包含尽量多的点。
看到这样一道题,猛一下真的觉得无从下手,上来能够想到的办法就是枚举上下边界,然后再枚举左右边界,然后统计点数。复杂度是O(n5)。显然无法承受。
因此,我们需要一点点仔细的分析,试图将这个问题往以前做过的类似的问题上转化。相似度最高的问题就是“最大连续和问题”,里面用到了一个“前缀和”的技巧。然而这道题好像并不是那么容易就转化到这个简单问题上。因此,我们不妨先思考一下当前遇到的困难是什么。
-
需要统计哪些信息?怎么统计?
这毫无疑问是我们当前遇到的最大问题,此时想想刚才的“笨拙”的枚举方法:枚举边界。我们这时可以考虑假设上下边界当前我已经确定了,该怎么做这道题?此时应该有一点模糊的思路了,利用扫描线的思想,统计第i条扫描线左边且在上下边界上的点数(但不包含在第i条线的点),同时还要统计第i条扫描线上有多少点。
然而又发现这样做还不能得到答案,因为点会重复计数,要想办法减去重复的点。此时,一个策略是再开一个数组,表示在第i条扫描线上,但不包含上下边界的点的个数。这样,这个简单一点的问题终于有了比较清晰的思路。
接下来的问题:如何统计?说的更具体一点:
2.需要哪些数组?维数怎么确定?怎么高效地计算它们的值?
我们尝试着按照刚刚找到的思路继续往下走。由于上下边界已经确定了,那么唯一的变量就是扫描线自身了,因此只需要一维数组即可。用Left[i]表示第i条扫描线左侧(不包含第i条扫描线)且在上下边界的点的个数,on2[i]表示第i条线上且在上下边界之间的点的个数,on[i]表示在第i条线上但不包含上下边界的点的个数。这样,假设还知道一个竖线j(j<i),那么ans=Left[i]-Left[j]+on2[i]+on[j]。终于,我们推出了这个问题的第一个公式,算是小有成就。那么如何计算呢?
考虑到坐标范围非常宽,但点的个数比较少,自然想到了“离散化”的思想。显然,x,y都要离散化处理,即把他们都放到一个结构体数组中,用P[i].x,P[i].y表示坐标。但是为了枚举上下边界,我们需要专门把所有的y放到一个数组,命名为y数组。所有的点放到结构体数组中存储,这样就离散化结束了。这样还不够,枚举边界一定是有顺序的——立即想到给y数组排序。会不会有相同的y值?——立即想到还要去重处理。这样,最终的y数组可以保证相邻的两个y值一定不同,枚举上下边界得以实现。
然而我们到现在依然没有实现高效的计算,还要继续探索。接下来就是枚举扫描线了。因为对x也离散化处理了,而扫描线一般是从左往右枚举——想到把所有的点按照x从小到大排序。然而多个不同的点可能都在一条竖线上,如何不重复的枚举扫描线呢?一个策略是寻找充分条件。可以发现,如果i==0或者第i个点的x和前一个不相同,即P[i].x!=P[i-1].x时,就说明遇到了一条新的扫描线,可以增加一个变量k,专门统计已经找到的扫描线条数。
这些工作都处理完后,我们已经知道如何计算on数组和on2数组了——根据表示的含义统计即可。Left数组如何计算?我们发现,Left数组是可以递推的:Left[i]=Left[i-1]+on2[i]-on[i],成功用上了前缀和!其实这要取决于如何设置合适的数组。设置不合理就没有这么简单的事情了。
终于,快接近答案了。因为ans=max(ans,Left[i]-Left[j]+on2[i]+on[j])当i固定时,我们需要让on[j]-Left[j]尽量大。这就转化为“输入一个序列,找两个不同的下标i,j(i<j)使得Ai-Aj尽量大”的经典问题。做法是先更新ans,再更新on[j]-Left[j]。
这样,在两个上下边界确定的情况下,我们成功找到了计算ans的方法。当枚举完所有的上下边界后,最终的ans就是答案。至此,问题获得圆满解决。
之所以写这篇文章,是因为这道题是我见过的利用扫描线解决的比较难的一道题。整个过程需要先进行数学建模,然后才能代码实现。前面的数组的设计和计算都是本题能否解成功解决的关键点。整道题我觉得最值得学习的就是这个建模过程,非常完美的解决了整个问题,并将时间复杂度优化至O(n3),在可承受的范围之内。ACM中,最难的其实不在代码实现,而是在建模过程,本题算是一个非常好的范例。
代码实现:点击打开链接