G54 半平面交 双端队列
视频链接:https://www.bilibili.com/video/BV1jL411C7Ct/
1. Luogu P4196 [CQOI2006]凸多边形 /【模板】半平面交
题意:逆时针给出 n 个凸多边形的顶点坐标,求它们交的面积
思路:
- 先求半平面交的边界线
- 再求由边界线构成的凸多边形的面积
时间:nm*log(nm)=500*log500
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=510; const double eps=1e-12; int n,m,cnt; struct Point{double x,y;} p[N]; struct Line{Point s,e;} a[N],q[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 t){ //数乘 return {a.x*t,a.y*t}; } double operator*(Point a,Point b){ //叉积 return a.x*b.y-a.y*b.x; } double angle(Line a){ //极角(-Pi,Pi] return atan2(a.e.y-a.s.y, a.e.x-a.s.x); } bool cmp(Line a, Line b){ //按极角+左侧排序 double A=angle(a), B=angle(b); return fabs(A-B)>eps ? A<B : (a.e-a.s)*(b.e-a.s)<0; } Point cross(Line a,Line b){ //直线交点 Point u=a.s-b.s, v=a.e-a.s, w=b.e-b.s; double t=u*w/(w*v); return a.s+v*t; } bool right(Line a,Line b,Line c){ //交点在直线右侧 Point p=cross(b,c); return (a.e-a.s)*(p-a.s)<0; } double half_plane(){ //半平面交 sort(a+1,a+cnt+1,cmp); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=cnt; i++){ //枚举直线 if(angle(a[i])-angle(a[i-1])<eps) continue; while(h<t && right(a[i],q[t],q[t-1]))t--; while(h<t && right(a[i],q[h],q[h+1]))h++; q[++t]=a[i]; } while(h<t && right(q[h],q[t],q[t-1]))t--; q[++t]=q[h]; //封口 int k=0; double res=0; for(int i=h;i<t;i++)p[++k]=cross(q[i],q[i+1]); for(int i=2;i<k;i++)res+=(p[i]-p[1])*(p[i+1]-p[1]); return res/2; //面积 } int main(){ scanf("%d",&n); while(n--){ scanf("%d",&m); for(int i=1;i<=m;i++)scanf("%lf%lf",&p[i].x,&p[i].y); for(int i=1;i<=m;i++)a[++cnt]={p[i],p[i%m+1]}; } printf("%.3lf\n", half_plane()); return 0; }
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=510; const double eps=1e-12; struct Point{ //点类 double x,y; Point(){} Point(double a,double b){x=a;y=b;} Point operator+(Point b){return Point(x+b.x,y+b.y);} Point operator-(Point b){return Point(x-b.x,y-b.y);} Point operator*(double b){return Point(x*b,y*b);} double operator*(Point b){return x*b.y-y*b.x;} }p[N]; struct Line{ //直线类 Point s,e; double ang; //极角(-Pi,Pi] Line(){} Line(Point a,Point b){s=a,e=b;ang=atan2((b-a).y,(b-a).x);} bool operator<(Line& b){ //按极角+左侧排序 return fabs(ang-b.ang)>eps ? ang<b.ang : (e-s)*(b.e-s)<0; } Point cross(Line& b){ //直线交点 Point u=s-b.s, v=e-s, w=b.e-b.s; double t=u*w/(w*v); return s+v*t; } bool right(Line& b,Line& c){ //交点在直线右侧 Point p=b.cross(c); return (e-s)*(p-s)<0; } }a[N],q[N]; int n,m,t; double half_plane(){ //半平面交 sort(a+1,a+n+1); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=n; i++){ //枚举直线 if(a[i].ang-a[i-1].ang<eps) continue; while(h<t && a[i].right(q[t],q[t-1]))t--; while(h<t && a[i].right(q[h],q[h+1]))h++; q[++t]=a[i]; } while(h<t && q[h].right(q[t],q[t-1]))t--; q[++t]=q[h]; //封口 int k=0; double res=0; for(int i=h;i<t;i++)p[++k]=q[i].cross(q[i+1]); for(int i=2;i<k;i++)res+=(p[i]-p[1])*(p[i+1]-p[1]); return res/2; //面积 } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&m); for(int i=1;i<=m;i++)scanf("%lf%lf",&p[i].x,&p[i].y); for(int i=1;i<=m;i++)a[++n]={p[i],p[i%m+1]}; } printf("%.3lf\n", half_plane()); return 0; }
2. Luogu P3194 [HNOI2008]水平可见直线
题意:给定 n 条直线 y=Ax+B,从上向下看,输出能看见哪些直线
思路:
- 从上向下看,能看见的一定是半平面交的边界线
- 让所有直线指向偏右方向,(0,B) 和 (1,A+B) 必过直线 y=Ax+B
- 交点在直线上不符合题意,要删除
- 本题第1条边一定不会被删除,单调队列已退化为单调栈。也不存在多余的尾巴。
时间:nlogn
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=50005; const double eps=1e-12; int n,ans[N]; struct Point{double x,y;}; struct Line{Point s,e; int id;}a[N],q[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 t){ //数乘 return {a.x*t,a.y*t}; } double operator*(Point a,Point b){ //叉积 return a.x*b.y-a.y*b.x; } double angle(Line a){ //极角(-Pi,Pi] return atan2(a.e.y-a.s.y, a.e.x-a.s.x); } bool cmp(Line a, Line b){ //按极角+左侧排序 double A=angle(a), B=angle(b); return fabs(A-B)>eps ? A<B : (a.e-a.s)*(b.e-a.s)<0; } Point cross(Line a,Line b){ //直线交点 Point u=a.s-b.s, v=a.e-a.s, w=b.e-b.s; double t=u*w/(w*v); return a.s+v*t; } bool right(Line a,Line b,Line c){ Point p=cross(b,c); return (a.e-a.s)*(p-a.s)<=0;//交点在直线上或右侧 } void half_plane(){ //半平面交 sort(a+1,a+n+1,cmp); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=n; i++){ //枚举直线 if(angle(a[i])-angle(a[i-1])<eps) continue; while(h<t && right(a[i],q[t],q[t-1]))t--; //while(h<t && right(a[i],q[h],q[h+1]))h++; q[++t]=a[i]; } int k=0; for(int i=h;i<=t;i++) ans[k++]=q[i].id; sort(ans,ans+k); for(int i=0;i<k;i++) printf("%d ",ans[i]); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ double A,B; scanf("%lf%lf",&A,&B); a[i]={{0,B},{1,A+B},i}; //指向偏右方 } half_plane(); return 0; }
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=50005; const double eps=1e-12; struct Point{ //点类 double x,y; Point(){} Point(double a,double b){x=a;y=b;} Point operator+(Point b){return Point(x+b.x,y+b.y);} Point operator-(Point b){return Point(x-b.x,y-b.y);} Point operator*(double b){return Point(x*b,y*b);} double operator*(Point b){return x*b.y-y*b.x;} }; struct Line{ //直线类 Point s,e; double ang; int id; Line(){} Line(Point a,Point b,int c){s=a,e=b;ang=atan2((b-a).y,(b-a).x);id=c;} bool operator<(Line& b){ //按极角+左侧排序 return fabs(ang-b.ang)>eps ? ang<b.ang : (e-s)*(b.e-s)<0; } Point cross(Line& b){ //直线交点 Point u=s-b.s, v=e-s, w=b.e-b.s; double t=u*w/(w*v); return s+v*t; } bool right(Line& b,Line& c){ Point p=b.cross(c); return (e-s)*(p-s)<=0; //交点在直线上或右侧 } }a[N],q[N]; int n,ans[N]; double half_plane(){ //半平面交 sort(a+1,a+n+1); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=n; i++){ //枚举直线 if(a[i].ang-a[i-1].ang<eps) continue; while(h<t && a[i].right(q[t],q[t-1]))t--; while(h<t && a[i].right(q[h],q[h+1]))h++; q[++t]=a[i]; } int k=0; for(int i=h;i<=t;i++) ans[k++]=q[i].id; sort(ans,ans+k); for(int i=0;i<k;i++) printf("%d ",ans[i]); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ double A,B; scanf("%lf%lf",&A,&B); a[i]={{0,B},{1,A+B},i}; //指向偏右方 } half_plane(); return 0; }
题意:长直赛道上有 n 辆赛车,给出它们的起始位置 ki 和速度 vi,问哪些赛车曾经处于领跑位置
思路:
- 以时间 t 为横轴,位置 s 为纵轴,画直线 s=v*t+k,将是一些斜向右上方的直线
- 半平面交的边界线即答案
- 用 map 保存具有相同 (v,k) 的赛车
- 交点在直线上符合题意,所以不能删除
- 半平面交的区域一定在 y 轴右侧,所以补上负 y 轴
- 本题第1条边一定不会被删除,单调队列已退化为单调栈。也不存在多余的尾巴。
时间:nlogn
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <map> #include <vector> #define double long double //交点坐标很大 using namespace std; const int N=10010; const double eps=1e-18; struct Point{double x,y;}; struct Line{ Point s,e; vector<int> id; //这条直线对应的所有赛车 }a[N],q[N]; int n,cnt,k[N],v[N],ans[N]; map<pair<int, int>, vector<int>> mp; 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 t){ //数乘 return {a.x*t,a.y*t}; } double operator*(Point a,Point b){ //叉积 return a.x*b.y-a.y*b.x; } double angle(Line& a){ //极角(-Pi,Pi] return atan2(a.e.y-a.s.y, a.e.x-a.s.x); } bool cmp(Line& a, Line& b){ //按极角+左侧排序 double A=angle(a), B=angle(b); return fabs(A-B)>eps ? A<B : (a.e-a.s)*(b.e-a.s)<0; } Point cross(Line& a,Line& b){ //直线交点 Point u=a.s-b.s, v=a.e-a.s, w=b.e-b.s; double t=u*w/(w*v); return a.s+v*t; } bool right(Line& a,Line& b,Line& c){//交点在右侧 Point p=cross(b,c); return (a.e-a.s)*(p-a.s)<0; } void half_plane(){ //半平面交 sort(a+1,a+cnt+1,cmp); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=cnt; i++){ //枚举直线 if(angle(a[i])-angle(a[i-1])<eps)continue; while(h<t && right(a[i],q[t],q[t-1]))t--; //while(h<t && right(a[i],q[h],q[h+1]))h++; q[++t]=a[i]; } int k=0; for(int i=h;i<=t;i++) for(int id: q[i].id) ans[++k]=id; sort(ans+1, ans+k+1); printf("%d\n", k); for(int i=1;i<=k;i++) printf("%d ", ans[i]); } int main(){ scanf("%d", &n); for(int i=1;i<=n;i++) scanf("%d", &k[i]); for(int i=1;i<=n;i++) scanf("%d", &v[i]); for(int i=1;i<=n;i++) mp[{v[i],k[i]}].push_back(i); a[++cnt]={{0,1},{0,0}}; //补负y轴 for(auto &[b,c]:mp) //{(0,k),(1,v+k),i} a[++cnt]={{0,b.second},{1,b.first+b.second},c}; half_plane(); }
4. POJ3335 Rotating Scoreboard
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N=110; const double eps=1e-12; int n; struct Point{ double x,y; Point(){} Point(double a,double b){x=a; y=b;} }p[N]; struct Line{ Point s,e; Line(){} Line(Point a, Point b){s=a; e=b;} }a[N],q[N]; Point operator+(Point a,Point b){ //向量+ return Point(a.x+b.x,a.y+b.y); } Point operator-(Point a,Point b){ //向量- return Point(a.x-b.x,a.y-b.y); } Point operator*(Point a,double t){ //数乘 return Point(a.x*t,a.y*t); } double operator*(Point a,Point b){ //叉积 return a.x*b.y-a.y*b.x; } double angle(Line& a){ //极角(-Pi,Pi] return atan2(a.e.y-a.s.y, a.e.x-a.s.x); } bool cmp(Line& a, Line& b){ //按极角+左侧排序 double A=angle(a), B=angle(b); return fabs(A-B)>eps ? A<B : (a.e-a.s)*(b.e-a.s)<0; } Point cross(Line& a,Line& b){ //直线交点 Point u=a.s-b.s, v=a.e-a.s, w=b.e-b.s; double t=u*w/(w*v); return a.s+v*t; } bool right(Line& a,Line& b,Line& c){//交点在直线右侧 Point p=cross(b,c); return (a.e-a.s)*(p-a.s)<0; } bool half_plane(){ //半平面交 sort(a+1,a+n+1,cmp); int h=1, t=1; q[1]=a[1]; for(int i=2; i<=n; i++){ //枚举直线 if(angle(a[i])-angle(a[i-1])<eps) continue; while(h<t && right(a[i],q[t],q[t-1]))t--; while(h<t && right(a[i],q[h],q[h+1]))h++; q[++t]=a[i]; } while(h<t && right(q[h],q[t],q[t-1]))t--; return t-h>=2; //多边形有核 } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y); for(int i=1;i<=n;i++) a[i]=Line(p[i%n+1],p[i]); if(half_plane()) printf("YES\n"); else printf("NO\n"); } return 0; }
练习