G53 旋转卡壳
视频链接:https://www.bilibili.com/video/BV1D54y1M7Tt/
1. Luogu P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳
思路:距离最远的点一定是凸壳上的两点
双指针枚举,i指针枚举凸壳的边,j指针在前面枚举最远点,优选答案
注意,两个指针都是向前走的,保证旋转卡壳时间为O(n)
时间:O(n*logn + n)
#include <iostream> #include <cstring> #include <algorithm> using namespace std; #define N 50010 #define x first #define y second #define Point pair<int,int> Point p[N],s[N]; int n; int cross(Point a,Point b,Point c){ //叉积 return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } int dis(Point a, Point b){ //距离平方 return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } void Andrew(){ sort(p+1,p+n+1); int t=0; for(int i=1; i<=n; i++){ //下凸包 while(t>1&&cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } int k=t; for(int i=n-1; i>=1; i--){ //上凸包 while(t>k&&cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } n=t-1; //n为凸包上的点数 } int rotating_calipers(){ //旋转卡壳 int res=0; for(int i=1,j=2; i<=n; i++){ while(cross(s[i],s[i+1],s[j])<cross(s[i],s[i+1],s[j+1]))j=j%n+1; res=max(res,max(dis(s[i],s[j]),dis(s[i+1],s[j]))); } return res; } int main(){ scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d%d",&p[i].x,&p[i].y); Andrew(); printf("%d\n",rotating_calipers()); return 0; }
2. Luogu P4166 [SCOI2007]最大土地面积
思路:内接四边形的对角线一定是凸包的对角线
枚举凸包的对角线,旋转卡壳求最远点a,b,更新答案
时间:O(n*logn + n*n)
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; #define N 2010 #define x first #define y second #define Point pair<int,int> Point p[N],s[N]; int n; double cross(Point a,Point b,Point c){ //叉积 return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } void Andrew(){ sort(p+1,p+n+1); int t=0; for(int i=1; i<=n; i++){ //下凸包 while(t>1 && cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } for(int k=t, i=n-1; i>=1; i--){ //上凸包 while(t>k && cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } n=t-1; //n为凸包上的点数 } double rotating_calipers(){ //旋转卡壳 double ans=0; for(int i=1; i<=n; i++){ int a=i, b=i+1; //a为i到j之间的点, b为j到i之间的点 for(int j=i+1; j<=n; j++){ while(cross(s[i],s[j],s[a+1])<cross(s[i],s[j],s[a]))a=a%n+1; while(cross(s[i],s[j],s[b+1])>cross(s[i],s[j],s[b]))b=b%n+1; ans=max(ans,-cross(s[i],s[j],s[a])+cross(s[i],s[j],s[b])); } } return ans/2; } int main(){ scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%lf%lf",&p[i].x,&p[i].y); Andrew(); printf("%.3lf\n", rotating_calipers()); return 0; }
3. Luogu P3187 [HNOI2007]最小矩形覆盖
思路:最小矩形的某条边一定在凸壳上的某条边上
枚举凸壳的边,旋转卡壳求三点
时间:O(n*logn + n)
#include<cstring> #include<iostream> #include<algorithm> #include<cmath> using namespace std; const double eps=1e-8; const double PI=acos(-1); #define N 50010 #define x first #define y second #define Point pair<int,int> Point p[N],s[N]; int n; Point operator+(Point a, Point b){ //向量+ return {a.x+b.x, a.y+b.y}; } Point operator-(Point a, Point b){ //向量- return {a.x-b.x, a.y-b.y}; } Point operator*(Point a, double p){ //数乘 return {a.x*p, a.y*p}; } double cross(Point a,Point b,Point c){ //叉积 return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } double dot(Point a,Point b,Point c){ //点积 return (b.x-a.x)*(c.x-a.x)+(b.y-a.y)*(c.y-a.y); } double dis(Point a,Point b){ //距离 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } Point rotate(Point a, double b){ //逆转角 return {a.x*cos(b)-a.y*sin(b), a.x*sin(b)+a.y*cos(b)}; } bool cmp(Point a, Point b){ //找最低点 return fabs(a.y-b.y)>eps ? a.y<b.y : a.x<b.x; } void zero(Point &a){ //避免-0.00000 if(fabs(a.x)<=eps) a.x=0; if(fabs(a.y)<=eps) a.y=0; } void Andrew(){ sort(p+1,p+n+1); int t=0; for(int i=1; i<=n; i++){ //下凸包 while(t>1 && cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } for(int k=t, i=n-1; i>=1; i--){ //上凸包 while(t>k && cross(s[t-1],s[t],p[i])<=0)t--; s[++t]=p[i]; } n=t-1; //n为凸包上的点数 } void rotating_calipers(){ //旋转卡壳 double ans=1e20; int a,b,c; a=b=2; //上a,右b,左c for(int i=1;i<=n;i++){ while(cross(s[i],s[i+1],s[a])<cross(s[i],s[i+1],s[a+1]))a=a%n+1; while(dot(s[i],s[i+1],s[b])<dot(s[i],s[i+1],s[b+1]))b=b%n+1; if(i==1)c=a; while(dot(s[i+1],s[i],s[c])<dot(s[i+1],s[i],s[c+1]))c=c%n+1; double d=dis(s[i],s[i+1]); double H=cross(s[i],s[i+1],s[a])/d; double R=dot(s[i],s[i+1],s[b])/d; double L=dot(s[i+1],s[i],s[c])/d; if(ans>(R+L-d)*H){ ans=(R+L-d)*H; p[1]=s[i+1]+(s[i]-s[i+1])*(L/d); p[2]=s[i]+(s[i+1]-s[i])*(R/d); p[3]=p[2]+rotate(s[i+1]-s[i],PI/2)*(H/d); p[4]=p[1]+rotate(s[i+1]-s[i],PI/2)*(H/d); } } printf("%.5lf\n",ans); int k=1; for(int i=2;i<=4;i++) if(cmp(p[i],p[k]))k=i; //找最低点 for(int i=1; i<=4; i++){ zero(p[k]); printf("%.5lf %.5lf\n",p[k].x,p[k].y); k=k%4+1; } } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y); Andrew(); rotating_calipers(); return 0; }
练习题
#include <iostream> #include <cstring> #include <algorithm> using namespace std; #define N 50010 #define x first #define y second #define Point pair<int,int> Point p[N],s[N]; int n,top; int cross(Point a,Point b,Point c){ //叉积 return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); } void Andrew(){ sort(p+1,p+n+1); top=0; for(int i=1; i<=n; i++){ //下凸包 while(top>1&&cross(s[top-1],s[top],p[i])<=0)top--; s[++top]=p[i]; } int t=top; for(int i=n-1; i>=1; i--){ //上凸包 while(top>t&&cross(s[top-1],s[top],p[i])<=0)top--; s[++top]=p[i]; } n=top-1; //n为凸包上的点数 } int rotating_calipers(){ //旋转卡壳 int res=0; for(int i=1; i<=n; i++){ int k=i+1; //k为j到i之间的点 for(int j=i+1; j<=n; j++){ while(cross(s[i],s[j],s[k+1])>cross(s[i],s[j],s[k]))k=k%n+1; res=max(res,cross(s[i],s[j],s[k])); } } return res; } int main(){ while(scanf("%d",&n),n!=-1){ for(int i=1; i<=n; i++) scanf("%d%d",&p[i].x,&p[i].y); Andrew(); printf("%.2f\n",rotating_calipers()/2.); } return 0; }