G54 半平面交 双端队列

视频链接:https://www.bilibili.com/video/BV1jL411C7Ct/

1. Luogu P4196 [CQOI2006]凸多边形 /【模板】半平面交

题意:逆时针给出 n 个凸多边形的顶点坐标,求它们交的面积

思路:

  1. 先求半平面交的边界线
  2. 再求由边界线构成的凸多边形的面积

时间: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,从上向下看,输出能看见哪些直线

思路:

  1. 从上向下看,能看见的一定是半平面交的边界线
  2. 让所有直线指向偏右方向,(0,B) 和 (1,A+B) 必过直线 y=Ax+B
  3. 交点在直线上不符合题意,要删除
  4. 本题第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;
}

3. Luogu P3256 [JLOI2013]赛车

题意:长直赛道上有 n 辆赛车,给出它们的起始位置 ki 和速度 vi,问哪些赛车曾经处于领跑位置

思路:

  1. 以时间 t 为横轴,位置 s 为纵轴,画直线 s=v*t+k,将是一些斜向右上方的直线
  2. 半平面交的边界线即答案
  3. 用 map 保存具有相同 (v,k) 的赛车
  4. 交点在直线上符合题意,所以不能删除
  5. 半平面交的区域一定在 y 轴右侧,所以补上负 y 轴
  6. 本题第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;
}

 

练习

Luogu P3222 [HNOI2012]射箭

Luogu P2600 [ZJOI2008]瞭望塔

Luogu P4250 [SCOI2015]小凸想跑步

Luogu P3297 [SDOI2013] 逃考

 

posted @ 2023-02-27 15:52  董晓  阅读(281)  评论(0编辑  收藏  举报