[学习笔记]day4-倍增/平面最近点对/递归树/HDU431(杂七杂八)
倍增
https://www.acwing.com/problem/content/111/
题意:给定\(m\),对于一个集合\(S\),取出其中的\(m\)对元素(如果少于\(2m\)个元素就取到不能取为止),每一对元素做差平方再求和,对应最大可能的结果为集合的“校验值”,现在给你一个长度为\(n\)的数列,希望分割成若干段使得每一段的校验值不超过\(T\),问至少分几段,\(n,m\leq 5\times 10^5\)。
题解:容易分析出这个校验值的计算方法:将集合的元素按大小排序,每次取出最大的和最小的配对。接着为了让划分段数尽量小,那从左往右考虑,我们就希望每一段尽可能长,于是就变成考虑给定左端点\(l\),右端点最多能扩展到哪里。
嗯…?等一等,为什么这么“贪心”地从左到右让每段尽可能长是对的?想一想确实是对的:假设现在的\(l\)最多能够扩展到\(r\)的位置,\(f(l,r)\leq T\Rightarrow f(l,r-1)\leq T\),那我们猜想会不会出现把\(a_r\)丢到下一个段里面,答案会更优,如果不会的话我们的结论就是正确的。事实上确实不会,假设原本后一段最多能扩展成\((r+1,q)\),现在加了一个元素\(a_r\),如果\(a_r\)也参与到了“校验值”的运算,那么校验值一定不可能被缩小,甚至很可能被扩大(即往一个集合里丢东西,校验值一定是不会变小的,因为计算方式是取最大)。
嗯于是这个问题就解决了,现在就只要考虑如何扩展区间…嗯,话说上面的分析似乎有很明显的单调性的味道:大区间可以小区间一定可以。二分位置似乎是一个可行的做法,每次扩展一个长度为\(L\)的区间的复杂度是\(O(L\log ^2 L)\)。
不过这里还有另一种做法,那就是倍增啦~这里用倍增的好处在于可以一边维护一个当前的\([l,r]\)和想要继续扩展的一段\([r+1,r+p]\),check的时候可以直接\(O(p)\)地来合并以及检验,复杂度可以降一个log。
最终复杂度,这里就直接考虑均摊的情况:最后划分成了\(K\)段,每段均摊的长度是\(\frac{N}{K}\),总的复杂度就是\(O(\sum \frac{N}{K}\log \frac{N}{K})=O(\frac{N}{K}\log^K\frac{N}{K})=O(N\log \frac{N}{K})\)。
平面最近点对
问题描述:平面内\(n\)个点,求出距离最近的点对。\(n\leq 10^6\)。
(对应的模板题:https://www.luogu.com.cn/problem/P1429)
我会暴力!:我会\(O(n^2)\)地枚举!没准就n方过百万了呢!
我会分治!
这里考虑分治的依据同样在于,如果我们把点集划分成两个集合\(S_1,S_2\),答案要么来自\(S_1,S_2\)二者之一,要么就是一个点在\(S_1\)内,另一个点在\(S_2\)内(是不是有点像最大子段和)。和其他分治一样,难点还是在于如何快速合并。
暴力地枚举点对,合并的渐进复杂度还是\(O(n^2)\)的,最后总复杂度还会多一个log,这样显然不行,于是我们就得考虑可能成为答案的点还需要满足哪些约束啦。
一维情况的分治:
一维情况很显然可以\(O(n\log n)\)排序再\(O(n)\)地扫一遍。不过我们稍微绕一下,考虑一维情况如何分治地处理。
首先同样是一遍按坐标排序,分成左右两段区间\([l,mid],[mid+1,r]\),递归地处理\(d_1=f(l,mid),d_2=f(mid+1,r)\),记\(D=min(d_1,d_2)\),现在考虑出现第三种情况(最近点对的一个点在左,一个点在右边)会有哪些约束(一个取\(mid\),另一个取\(mid+1\),好了每次合并\(O(1)\)地解决啦x肯定不能这么说,这样子就没法拓展了):以\(mid\)位置上的点\(p_m\)为分界,假设可能成为答案的点分别为\(p_l,p_r\),那一定有\(dis(p_l,p_m)<D,dis(p_r,p_m)<D\).
好了可以拓展了
二维情况也类似地,我们处理出左右区间的\(d_1=f(l,mid),d_2=f(mid+1,r)\),取\(mid\)位置的点\(p_m\),以它为分界,做一条垂直\(x\)轴的直线,那样满足条件的点对一定在\([x_m-D,x_m+D]\)这个范围内,嗯…不过仔细想想这样子的点数还是可能达到\(O(n)\)级别的,但是还有别的约束吧,比如对于左边的一个点\(p_l\),右边可能和它配对成为答案的点\(p_r\)一定满足\(y_r\in[p_l-D,p_l+D]\)这个约束,结合前一个约束,对左边每个点,右边可能成为答案的点就落在一个水平长度为\(D\),竖直长度为\(2D\)的正方形内了,而这里面的点一定不超过6个:首先对于左边一个点,右边可能成为答案的点集组成的应该是一个弓形,数学直觉告诉我们这里面应该不可能同时存在太多的点,事实也确实如此,基于这个想法,我们把弓形补成一个矩形,矩形的长边至多\(2D\),短边至多\(D\),再分成大小相同的6个\(\frac{1}{2}D\times \frac{2}{3}D\)的小矩形,每个矩形内点的最远距离是\(\frac{5}{6}D<D\),于是由鸽巢原理我们就知道右边最多有6个可能和组成\(p_l\)答案的点。
于是这样就可以\(O(n)\)地解决了!
嗯…等一下,怎么快速找到这6个点,每次合并的时候再排序嘛…这样似乎就变成\(T(n)=2T(n/2)+O(n\log n)\)了,考虑对应的递归树,第\(k\)层\(t=2^{k-1}\)个点的话,第\(k\)层对应的代价就是
\(\begin{aligned}O(\sum_{i=1}^t \frac{n}{t}\log\frac{n}{t})=O(\frac{n}{t}\log^t\frac{n}{t})=O(n\log\frac{n}{t})=O(n\log n-nk)\end{aligned}\)
最后的答案就是\(\begin{aligned}O(\sum_{i=0}^{\log n}(n\log n-ni))=O(n\log^2 n)\end{aligned}\),嗯似乎不是那么优美…(毕竟我可是听说这个问题有\(O(n\log n)\)的做法),嗯看来每次都要排序这个东西得解决一下…
在解决之前插一嘴(毕竟这篇blog就当做复习啦,各种扯远也很正常x),发现算法导论关于主定理的证明一节,一道课后习题能对应直接给出这种递归式的解…对于\(T(n)=aT(n/b)+f(n)\),如果\(f(n)=\Theta(n^{\log _b a}\log ^k n)\),则\(T(n)=\Theta(n^{\log _b a}\log^{k+1} n)\),证明就类似上面递归树的分析(x)
好了现在言归正传,我们要把每次合并优化到线性,似乎也好做:类似归并排序,每次递归顺带维护一个按照\(y\)值从小到大排序的数列,这个东西可以做到\(O(n)\)的合并。于是就…等一下,按\(y\)排好序之后\(x\)怎么办,如果不在\(x\in[x_m-D,x_m+D]\)的前提下找,那样就没法保证6个点这个性质了,这样就可能出现对一个\(p_l\),右边的点会有很多个\(x\)就不满足条件的点要跳过,跳完之后对下一个\(p_{l+1}\)又要跳回去再重新跳,复杂度看起来有点危…还要再稍微处理一下。
那就先把\(x\)满足条件的点再另外挑出来,再去用类似双指针的东西来扫描,问题解决√
获得了『\(O(n\log n)\)的算法』(突然中二)
算导上的做法
实现的细节和算导上的稍微有点不同,算导上是先处理一个按\(y\)排序的数组,每次分治的时候把这个数组\(O(n)\)地拆开,同时书上是直接把左右两边的点合起来处理了(对每个点枚举周围的7个点),原理还是一样的。
另外书上还讨论了怎么处理重复点以及一些拓展问题。
最后丢个代码吧
double solve(int l,int r){
if(l==r)return (1e12);
int mid=(l+r)>>1;
double d=min(solve(l,mid),solve(mid+1,r));
if(ps[mid+1].x-ps[mid].x>d)return d;
vl.clear();vr.clear();
rep(i,l,mid)if(ps[mid].x-qs[i].x<=d)vl.pb(qs[i]);
rep(i,mid+1,r)if(qs[i].x-ps[mid].x<=d)vr.pb(qs[i]);
int p=0,q=0,sl=vl.size(),sr=vr.size();
while(p<=sl-1){
while(q<sr-1&&vr[q].y<vl[p].y-d)q++;
rep(j,q,min(q+5,sr-1))d=min(d,dis(vl[p],vr[j]));
p++;
}
inplace_merge(qs+l,qs+mid+1,qs+r+1,cmp2);
return d;
}
//ps存的是按x排好序的点,qs是在ps的基础上一边递归一边合并的按y排序的点
那就继续扯远-HDU4312
想着找点类似的东西来做,飘着飘着就飘到切比雪夫距离去了…然后就看到了一道题:http://acm.hdu.edu.cn/showproblem.php?pid=4312
想了一会就做掉了,连着也塞进来吧(x)
题意:给平面内\(n(n\leq 10^5)\)个点,找一个点使得其他点到它的距离的切比雪夫距离(\(max(\Delta x,\Delta y)\))之和最小。
题解:一开始有点被吓到,其实冷静一想可以把切比雪夫距离转化成曼哈顿距离(旋转45°+缩小\(\sqrt 2\)倍),接着为了方便处理再把数据都乘上2(这样就都是整数了,最后再除掉就好),于是变成找一个点使得其他点到它的曼哈顿距离之和最小,也就是\(\sum |x_i-x_p|+|y_i-y_p|\)最小,\(x,y\)可以分开统计,接着对\(x\)排序,就可以\(O(n)\)地求出每个点作为\(p\)点的答案,对\(y\)做同样的处理,这题就愉快滴做完啦~