1. 程式人生 > 遊戲 >Steam開啟DC遊戲特惠活動:瘋狂特賣低至1折

Steam開啟DC遊戲特惠活動:瘋狂特賣低至1折

說起來簡單但是寫起來還是好麻煩。。。

首先半平面交是指給出多條向量和起點,規定只取向量的左側或右側,求最終得到的交。

我們只需要維護交的凸包上的直線和它們的交點。

可以 \(O(n^2)\) 地暴力,但甚至 \(O(n\log n)\) 還要好寫點??


例題

把所有點逆時針排序,最後的交就是所有直線左半平面的交

首先把所有直線搞出來,用起點和向量表示

for(int i=1;i<=n;i++){
		m=in;
		for(int j=1;j<=m;j++)x[j]=in,y[j]=in;
		for(int j=1;j<m;j++)a[++an]=Line({x[j],y[j]},{x[j+1]-x[j],y[j+1]-y[j]});
		a[++an]=Line({x[m],y[m]},{x[1]-x[m],y[1]-y[m]});
	}

我們的思路是按極角加入直線並維護一個凸包,這樣會比雜亂無章的插入舒服很多,先把所有直線按向量極角排序

排序時我們注意到,如果有兩條直線極角相同,我們需要保留靠左的直線,這裡不能只看橫縱座標,而應用叉乘正負判斷。如果有兩條線在同一直線,因為我們維護的是直線,所以可以任取。

struct vec{// point/vector
	double x,y;
	vec(){}
	vec(double inx,double iny):x(inx),y(iny){}
	vec operator+(cs vec a){return {x+a.x,y+a.y};}
	vec operator-(cs vec a){return {x-a.x,y-a.y};}
	vec operator*(cs double b){return {x*b,y*b};}
	vec operator/(cs double b){return {x/b,y/b};}
}b[N];
double dotp(vec a,vec b){return a.x*b.x+a.y*b.y;}// dot product 
double crsp(vec a,vec b){return a.x*b.y-a.y*b.x;}//cross product
struct Line{
	vec s,e;//s->point,e->vector
	double K;
	Line(){}
	Line(vec S,vec E):s(S),e(E),K(atan2(E.x,E.y)){}
	bool operator<(cs Line a)//Keep the line near to left
		{return (K-a.K>eps||K-a.K<-eps)?K>a.K:crsp(a.e,{s.x-a.s.x,s.y-a.s.y})>0;}
}a[N],q[N];

把直線排序之後就去重

	sort(a+1,a+1+an);a[0].K=10000000000,m=0;		
	for(int i=1;i<=an;i++)
		if(a[i].K-a[i-1].K<-eps)a[++m]=a[i];

接下來用一個雙端佇列維護凸包上的邊,再維護它們的交點。

然後我們自己畫畫圖,可以發現由於我們是按極角加邊,所以一次加邊對隊尾的影響只有兩種情況:

若是上一個交點在當前直線右邊,那麼上一條邊就沒有用了,應該去掉。

若是上一個交點在當前直線左邊,那麼上一條直線就應該保留。


同時我們注意到一條邊還可能對隊頭的邊產生影響,再畫畫圖,我們同樣注意到第一條直線的去留和第一個交點是否在當前直線左邊有關


最後,因為我們一直是用新的直線來幹掉隊尾的直線,所以可能會出現隊尾最後沒有去幹淨的情況

這種情況發現所有多餘的邊與上一條邊的交點都在隊頭直線的右邊。


所以維護凸包時,我們只需要,判斷隊尾,判斷隊首,加入直線,維護交點。
最後再把多餘的直線幹掉。

for(int i=1;i<=m;i++){
		while(tl>hd&&rightof(b[tl],a[i]))tl--;
		while(hd<tl&&rightof(b[hd+1],a[i]))hd++;
		q[++tl]=a[i];
		if(tl>hd)b[tl]=getcrspt(q[tl-1],q[tl]);
	}
	while(rightof(b[tl],q[hd]))tl--;

其中的求交點要小心豎直不存在斜率的直線

bool rightof(vec t,Line p){//if the point is on the right of the line 
	t.x-=p.s.x,t.y-=p.s.y;
	if(crsp(p.e,t)<=0)return true;
	return false;
}
vec getcrspt(Line x,Line y){//to get the cross point of two lines
	if(abs(x.e.x)>abs(y.e.x))swap(x,y);
	if(x.e.x<=eps&&x.e.x>=-eps){	
		double k2=y.e.y/y.e.x,b2=y.s.y-y.s.x*k2;
		return {x.s.x,x.s.x*k2+b2};	
	}
	double k1=x.e.y/x.e.x,b1=x.s.y-x.s.x*k1;
	double k2=y.e.y/y.e.x,b2=y.s.y-y.s.x*k2;
	return {(b2-b1)/(k1-k2),k1*(b2-b1)/(k1-k2)+b1};
}

好了,現在你得到了凸包上的 \(n\) 條直線和 \(n-1\) 個交點,並且由於我們是按極角排序加邊,這些點都是排好序的,所以直接三角剖分算面積。(叉乘的幾何意義是兩向量夾的平行四邊形面積)

vec t=getcrspt(q[hd],q[tl]);
	for(int i=hd+1;i<=tl;i++)
		b[i].x-=t.x,b[i].y-=t.y;
	for(int i=hd+1;i<tl;i++)
		ans+=crsp(b[i],b[i+1]);
	cout<<fixed<<setprecision(3)<<ans/2;

ok,這樣這道模板題就可以過了。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define cs const
#define in read() 
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
cs double eps=1e-18;
cs double pi=acos(-1);
cs int N=505; 
struct vec{// point/vector
	double x,y;
	vec(){}
	vec(double inx,double iny):x(inx),y(iny){}
	vec operator+(cs vec a){return {x+a.x,y+a.y};}
	vec operator-(cs vec a){return {x-a.x,y-a.y};}
	vec operator*(cs double b){return {x*b,y*b};}
	vec operator/(cs double b){return {x/b,y/b};}
}b[N];
double dotp(vec a,vec b){return a.x*b.x+a.y*b.y;}// dot product 
double crsp(vec a,vec b){return a.x*b.y-a.y*b.x;}//cross product
struct Line{
	vec s,e;//s->point,e->vector
	double K;
	Line(){}
	Line(vec S,vec E):s(S),e(E),K(atan2(E.x,E.y)){}
	bool operator<(cs Line a)//Keep the line near to left
	{return (K-a.K>eps||K-a.K<-eps)?K>a.K:crsp(a.e,{s.x-a.s.x,s.y-a.s.y})>0;}
}a[N],q[N];
int hd=1,tl;
int an,n,m;
double x[55],y[55],ans;
bool rightof(vec t,Line p){//if the point is on the right of the line 
	t.x-=p.s.x,t.y-=p.s.y;
	if(crsp(p.e,t)<=0)return true;
	return false;
}
vec getcrspt(Line x,Line y){//to get the cross point of two lines
	if(abs(x.e.x)>abs(y.e.x))swap(x,y);
	if(x.e.x<=eps&&x.e.x>=-eps){	
		double k2=y.e.y/y.e.x,b2=y.s.y-y.s.x*k2;
		return {x.s.x,x.s.x*k2+b2};	
	}
	double k1=x.e.y/x.e.x,b1=x.s.y-x.s.x*k1;
	double k2=y.e.y/y.e.x,b2=y.s.y-y.s.x*k2;
	return {(b2-b1)/(k1-k2),k1*(b2-b1)/(k1-k2)+b1};
}
signed main(){
	n=in;
	
	for(int i=1;i<=n;i++){
		m=in;
		for(int j=1;j<=m;j++)x[j]=in,y[j]=in;
		for(int j=1;j<m;j++)a[++an]=Line({x[j],y[j]},{x[j+1]-x[j],y[j+1]-y[j]});
		a[++an]=Line({x[m],y[m]},{x[1]-x[m],y[1]-y[m]});
	}
	sort(a+1,a+1+an);a[0].K=10000000000,m=0;		
	for(int i=1;i<=an;i++)
		if(a[i].K-a[i-1].K<-eps)a[++m]=a[i];	
			
	for(int i=1;i<=m;i++){
		while(tl>hd&&rightof(b[tl],a[i]))tl--;
		while(hd<tl&&rightof(b[hd+1],a[i]))hd++;
		q[++tl]=a[i];
		if(tl>hd)b[tl]=getcrspt(q[tl-1],q[tl]);
	}
	while(rightof(b[tl],q[hd]))tl--;
	
	vec t=getcrspt(q[hd],q[tl]);
	for(int i=hd+1;i<=tl;i++)
		b[i].x-=t.x,b[i].y-=t.y;
	for(int i=hd+1;i<tl;i++)
		ans+=crsp(b[i],b[i+1]);
	cout<<fixed<<setprecision(3)<<ans/2;
	return 0;
}