G52 凸包 Andrew算法
视频链接:https://www.bilibili.com/video/BV1XL411C7Pf/
1. Luogu P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包
Andrew 算法
- 对所有点按坐标 x 为第一关键字、 y 为第二关键字排序。第1、第n两个点一定在凸包上。
- 先顺序枚举所有点,求下凸包。用栈维护当前在凸包上的点:新点入栈前,总要判断该弹出哪些旧点。只要新点处在由栈顶两点构成的有向直线的右侧或共线,就弹出旧点。不能弹出时,新点入栈。
- 再逆序枚举所有点,求上凸包。用栈维护同上。
注意:每个点入栈两次,出栈不超过两次,所以总次数不超过 。
注意:最后封口时,栈底和栈顶存的都是第一个点。
时间:O(nlogn)
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=100010; struct Point{double x,y;} p[N],s[N]; int n,top; 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 dis(Point a,Point b){ //距离 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } bool cmp(Point a, Point b){ //比较 return a.x!=b.x ? a.x<b.x : a.y<b.y; } double Andrew(){ sort(p+1,p+n+1,cmp); //排序 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]; } double res=0; //周长 for(int i=1; i<top; i++) res+=dis(s[i],s[i+1]); return res; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y); printf("%.2lf\n", Andrew()); return 0; }
2. Luogu P3829 [SHOI2012]信用卡凸包
思路:圆弧之和=圆的周长
直边之和=所有圆心构成的凸包的周长
时间:O(nlogn)
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #define x first #define y second using namespace std; const int N = 40010; const double pi=acos(-1.0); typedef pair<double,double> Point; Point p[N],s[N]; int n,top,cnt; 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 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)}; } double Andrew(){ sort(p+1,p+n+1); 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]; } double res=0; //周长 for(int i=1; i<top; i++) res+=dis(s[i],s[i+1]); return res; } int main(){ scanf("%d",&n); double a,b,r; scanf("%lf%lf%lf",&a,&b,&r); a=a/2-r, b=b/2-r; //圆心到中心的边距 int dx[]={1,1,-1,-1},dy[]={1,-1,-1,1}; //四角偏移量 while(n--){ double x,y,z; scanf("%lf%lf%lf",&x,&y,&z); for(int i=0; i<4; i++){ Point t=rotate({dx[i]*b,dy[i]*a},z); //圆心旋转 p[++cnt]={x+t.x,y+t.y}; //圆心平移 } } n=cnt; //圆心个数 printf("%.2lf\n", Andrew()+2*pi*r); return 0; }
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #define x first #define y second using namespace std; const int N = 40010; const double pi=acos(-1.0); typedef pair<double,double> Point; Point p[N],s[N]; int n,top,cnt; Point operator-(Point a,Point b){ //向量- return Point(a.x-b.x,a.y-b.y); } double operator*(Point a,Point b){ //叉积 return a.x*b.y-a.y*b.x; } 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)}; } double Andrew(){ sort(p+1,p+n+1); for(int i=1; i<=n; i++){ //下凸包 while(top>1&&(s[top]-s[top-1])*(p[i]-s[top-1])<=0)top--; s[++top]=p[i]; } int t=top; for(int i=n-1; i>=1; i--){ //上凸包 while(top>t&&(s[top]-s[top-1])*(p[i]-s[top-1])<=0)top--; s[++top]=p[i]; } double res=0; //周长 for(int i=1; i<top; i++) res+=dis(s[i],s[i+1]); return res; } int main(){ scanf("%d",&n); double a,b,r; scanf("%lf%lf%lf",&a,&b,&r); a=a/2-r, b=b/2-r; //圆心到中心的边距 int dx[]={1,1,-1,-1},dy[]={1,-1,-1,1}; //四角偏移量 while(n--){ double x,y,z; scanf("%lf%lf%lf",&x,&y,&z); for(int i=0; i<4; i++){ Point t=rotate({dx[i]*b,dy[i]*a},z); //圆心旋转 p[++cnt]={x+t.x,y+t.y}; //圆心平移 } } n=cnt; //圆心个数 printf("%.2lf\n", Andrew()+2*pi*r); return 0; }
练习:
思路:答案就是凸包周长 + 半径为 L 的圆的周长