半平面交大意:给一堆直线,每条直线能将平面切掉一半,求剩下的部分
这里介绍的是排序增量法qwq
前置芝士:
①叉积(a×b=a.x·b.y+a.y·b.x)
叉积不同于点积,叉积的结果是向量,而点积的结果是标量。
设平面直角座标系有三点A(x0,y0),B(x1,y1),C(x2,y2)。
有共起点p0的两个向量AB(x1-x0,y1-y0),AC(x2-x0,y2-y0),它们的叉积表示为AB×AC=(x1-x0)•(y2-y0)+(y1-y0)•(x2-x0)。
此时有三种情况:
- 若p0p1×p0p2>0,那么向量p0p1在向量p0p2的逆时针方向;
- 若p0p1×p0p2<0,那么向量p0p1在向量p0p2的顺时针方向;
- 若p0p1×p0p2=0,那么向量p0p1与向量p0p2共线。
如下图,a为p0p1,b为p0p2。
原因可以问问度娘(别问我就是了)
②直线交点
设直线 的起点为(,),终点为(,)。
那么有
易解得,(b的分子长得像个叉积)。
假设我们有两条直线和直线,的起点座标为(x_1,y_1),终点座标为(x_2,y_2),的起点座标为(x_3,y_3),终点座标为(x_4,y_4)。
由上可知
设,,,,,
代入得
那么可解得直线交点的横座标为 ,纵座标为 。
算法:
先逆时针连好直线,把这些直线进行极角排序。
维护一个双端队列,枚举每条直线,当已有直线不小于2时(即交点数不小于1)做以下操作:
- 与凸包有些类似,我们要检查队尾前一条直线与当前直线的交点是否优于队尾前一条直线与队尾直线的交点,即前者在后者的顺时针方向。如果是,则把队尾的直线出队。
- 同理,检查当前加入的直线与队头后面一条直线的交点是否在队头的直线与队头后面一条直线的交点的顺时针方向,如果是,则把队头的直线出队。
Q:如何判断前者在后者顺时针方向?
A:以队尾为例,因为队尾前一条直线与当前直线的交点必然在当前直线上,所以只要连一条当前直线起点到队尾前一条直线与队尾直线的交点的直线a,判断当前直线是否在a的顺时针方向即可。
Q:为什么是顺时针方向?
A:因为是逆时针连边,可以通过下图感性理解一下(图待补)
模板:
[CQOI2006]凸多边形
照着某位dalao写的。。。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define eps 1e-5
using namespace std;
int t,n,m;
typedef struct data{
double x,y;
data(double a = 0, double b = 0) {x = a, y = b;}
}nod,vec;
vec operator - (nod a, nod b) {return vec(b.x - a.x, b.y - a.y);}
double operator ^ (vec a, vec b) {return a.x * b.y - a.y * b.x;}//叉积
int sign(double x){
return fabs(x)<=eps ? 0 : (x>0 ? 1 : -1);
}
struct line{
nod st,ed;
double ang;
line(){}
line(nod a,nod b,double an){
st=a,ed=b,ang=an;
}
friend bool operator < (line x,line y){
return sign(x.ang-y.ang)==0 ? sign((x.ed-x.st) ^ (y.ed-x.st)) >0 : sign(x.ang-y.ang)<0;
//因为我是向量左侧求交,所以极角相同时靠左的更优,把优的放在后面,方便之后的操作 ,可以画图体会一下
}
};
nod p[20010],c[20010];
line l[20010],q[20010];
double get_angle(vec a){
return atan2(a.y,a.x);
}
double get_angle(nod a,nod b){
return atan2(b.y-a.y,b.x-a.x);
}
nod get_nod(line a,line b) {//求交点
double a1=a.st.x-a.ed.x,b1=a.ed.y-a.st.y,c1=a.st.x*a.ed.y-a.ed.x*a.st.y;
double a2=b.st.x-b.ed.x,b2=b.ed.y-b.st.y,c2=b.st.x*b.ed.y-b.ed.x*b.st.y;
return nod((a1*c2-a2*c1)/(a1*b2-a2*b1), (b1*c2-b2*c1)/(a2*b1-a1*b2));
}
bool check(line a,line b,line c){
nod o=get_nod(b,c);
if(((a.ed-a.st)^(o-a.st))<0) return true;
return false;
}
int head=0,tail=0,cnt=0;//模拟双端队列
bool Half_nod(){//判断是否有解
sort(l,l+n);//排序
//去重,极角相同时取最后一个。
for(int i=0;i<n-1;i++) {
if(fabs(l[i].ang-l[i+1].ang)<eps) continue;
l[cnt++]=l[i];
}
l[cnt++]=l[n-1];
for(int i=0;i<cnt;i++) {
//判断新加入直线产生的影响
while(tail-head>1&&check(l[i],q[tail-1],q[tail-2])) tail--;
while(tail-head>1&&check(l[i],q[head],q[head+1])) head++;
q[tail++]=l[i];
}
//最后判断最先加入的直线和最后的直线的影响
while(tail-head>1&&check(q[head],q[tail-1],q[tail-2])) tail--;
while(tail-head>1&&check(q[tail-1],q[head],q[head+1])) head++;
if(tail-head<3) return false;
return true;
}
bool judge() {//判断输入点的顺序,如果面积 <0,说明输入的点为逆时针,否则为顺时针
double sum=0;
for(int i=1;i<n-1;i++)
sum+=((p[i]-p[0])^(p[i+1]-p[0]));
return sum < 0;
}
double calcs(){//面积
int tot=0;
double sum=0;
if(tail-head<3)return 0;
for(int i=head;i<tail-1;i++)
c[++tot]=get_nod(q[i],q[i+1]);
c[++tot]=get_nod(q[tail-1],q[head]);
c[++tot]=c[1];
for(int i=1;i<=tot;i++)
sum+=(c[i]-c[1])^(c[i+1]-c[1]);
return sum/2.0;
}
int main(){
int tot=0;
scanf("%d", &n);
for(int i=n-1;i>=0;i--){
scanf("%d",&m);
for(int j=1;j<=m;j++)
scanf("%lf %lf",&p[tot++].x,&p[tot].y);
p[tot]=p[tot-m];
for(int i=tot-m;i<tot;i++)
l[i]=line(p[i],p[i+1],get_angle(p[i],p[i+1]));
}
n=tot;
/*if(judge()){
for(int i=0;i<n;i++)
l[i]=line(p[(i+1)%n],p[i]);
}
else{
for(int i=0;i<n;i++)
l[i]=line(p[i],p[(i+1)%n]);
}*/
//cout<<"sdhauijsdfgduyguswenjhsdfbsca jhi"<<endl;
Half_nod();
printf("%.3lf",calcs());
}
例题:
待续