半平面交大意:給一堆直線,每條直線能將平面切掉一半,求剩下的部分
這裏介紹的是排序增量法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());
}
例題:
待續