题解:P16223 【模板】旋转卡壳/最远点对
Colubrid_L · · 题解
废话时间
终于让我抢到模板题的题解了。
正文
前置知识
求凸包和平面向量的一些基础运算。
不会求凸包的移步【模板】二维凸包。不会平面向量基本运算的回去重修高中数学必修二。
注意到如果想要解决此类问题,你最好会向量叉乘。
算法思想
旋转卡壳 的核心思想是:
- 最远点对一定出现在点集的凸包上。
- 利用凸多边形“对踵点对”的性质,通过旋转两条平行线来遍历所有可能的最远点对,从而在
O(N) 时间内完成。
算法步骤
对于每组数据,假设我们已经求好了凸包上的顶点序列,此时遍历每条边
- 重复执行
k++直到k+1 到当前边的距离小于等于k 到当前边的距离。 - 计算点
k 到点i 的距离d_1 ,以及点k 到点i+1 的距离d_2 。除此之外还需考察k+1 与点i 及i+1 的答案,以防漏掉等距对踵点。
正确性证明
凸包的必要性
平面点集的直径(最远点对)一定位于凸包的顶点上。考虑使用反证法,若存在两点
旋转卡壳的可行性
对于凸多边形,定义“对踵点对”为存在两条平行支撑线分别经过两个点,且多边形位于两线之间。直径一定出现在某一对对踵点上。
旋转卡壳模拟一对平行线绕多边形旋转一周的过程:固定一条边(支撑线),找到离该边最远的点(即对踵点),然后旋转到下一组边。
考虑利用叉积的正负判断距离的增减趋势:对于边
但上面一段只能证明保持
由于我们遍历了所有边,所以应当不会出现遗漏情况。因此算法能够正确输出直径对应的点对。
复杂度证明
- 凸包构造:使用 Andrew 算法的话大概在
O(N \log N) ,瓶颈在排序。 - 旋转卡壳:指针
k 在整个扫描过程中总共移动不超过m 次(m 为凸包顶点数),每步常数时间,复杂度也在O(N) 级别。 - 总复杂度:此总体复杂度为
O(N \log N) 左右。可以通过本题。 - 空间复杂度:
O(N) 存储点集及凸包。
代码
最好注意一下精度问题,但是我太懒了,数据没卡我就不改了。如果加强数据了记得踹我。
具体的细节可能与上文有些出入,不过不影响理解。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
struct pt{
double x,y,id;
pt(){x=y=id=0;}
bool operator< (const pt &a)const{
if(a.x!=x)return x<a.x;
return y<a.y;
}
}p[N],stk[N];
double cross(pt a,pt b,pt c){
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dist(pt a,pt b){
return __builtin_sqrtl((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int n,top,T;
double ans;
void andrew(){
sort(p+1,p+n+1);
for(int i=1;i<=n;i++){
while(top>=2&&cross(stk[top-1],stk[top],p[i])<=0)
top--;
stk[++top]=p[i];
}
int tmp=top;
for(int i=n-1;i>=1;i--){
while(top>tmp&&cross(stk[top-1],stk[top],p[i])<=0)
top--;
stk[++top]=p[i];
}
top--;
}
void work(){
if(n==2){
cout<<0<<" "<<1<<'\n';
return ;
}
if(top==2){
cout<<stk[1].id-1<<" "<<stk[top].id-1<<'\n';
return ;
}
int ansl=1,ansr=2,j=3;
for(int lef=1;lef<=top;lef++){
int rig=(lef+1)%top;
if(!rig)rig=top;
while(true){
int nej=(j+1)%top;
if(!nej)nej=top;
double resa=cross(stk[lef],stk[rig],stk[j]);
double resb=cross(stk[lef],stk[rig],stk[nej]);
if(resa<resb)j=nej;
else break;
}
if(ans<dist(stk[lef],stk[j]))
ans=dist(stk[lef],stk[j]),ansl=stk[lef].id,ansr=stk[j].id;
if(ans<dist(stk[rig],stk[j]))
ans=dist(stk[rig],stk[j]),ansl=stk[rig].id,ansr=stk[j].id;
int nej=(j+1)%top;
if(!nej)nej=top;
if(ans<dist(stk[lef],stk[nej]))
ans=dist(stk[lef],stk[nej]),ansl=stk[lef].id,ansr=stk[nej].id;
if(ans<dist(stk[rig],stk[nej]))
ans=dist(stk[rig],stk[nej]),ansl=stk[rig].id,ansr=stk[nej].id;
}
cout<<ansl-1<<" "<<ansr-1<<'\n';
}
void solve(){
cin>>n,ans=top=0;
for(int i=1;i<=n;i++)
cin>>p[i].x>>p[i].y,p[i].id=i;
andrew(),work();
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>T;
while(T--)
solve();
return 0;
}
感谢阅读。