计算几何 - 你绝对找不到比这更好的计算几何

dzy哥哥回来给我们上课
杜老师好帅啊qwq

计算几何

  • 这些知识你需要自己推一遍
  • 向量的运算
  • 凸包
  • 旋转卡壳
  • 半平面交

重新写计算几何

这些知识你需要自己推一遍

  • 线段,及点在线段上的表示(两种向量法-一个叉乘一个等和线,一种距离法)
  • 直线及其表示,用点斜法存储(学习斜率优化)
struct node3{
	double x,y,k;
}line[NNN];
  • 直线引申为半平面
  • 角度
double angle[NNN];//角度表示,用弧度制,省略单位pi
  • 多边形:按顺序(顺/逆)存点,有时候也会用有向线段(或一个表示位置的点和其他边)
  • 圆:存圆心和半径
  • 两点之间的距离
  • 面积:用叉乘就很好做
  • 小心点到直线的距离和点到线段的距离有区别
  • 判断线段是否相交,用看旋转,判两次
  • 求比例用相似导一下就行了
  • 作垂线:平行+旋转
  • 作角平分线:用对称性
  • 判断点是否在多边形内部:连的射线与边的交点奇偶(奇在内,偶在外),注意特判一些情况
  • 与圆相关的点:用交点推一下就行了

练习
POJ1269
特判就特判,不是特殊情况再求交点

POJ3907
第一道计算几何提:D
code

#include
#include
#include
#include
#include
#include
using namespace std;
#define in Read()
#define re register
const int NNN=1e6+10;
int n;
struct node{
	double x,y;
	friend inline double operator * (const node &a,const node &b){
		return a.x*b.y-a.y*b.x;
	}
}v[NNN];
double ans;

int main(){
	while(scanf("%d",&n)!=EOF&&n){
		for(re int i=1;i<=n;i++)scanf("%lf%lf",&v[i].x,&v[i].y);
		ans=0;
		for(re int i=1;i<=n;i++)ans+=v[i]*v[i%n+1];
		printf("%d\n",(int)(fabs(ans)/2));
	}
	return 0;
}

向量的运算

以前的代码好丑啊,但是我懒得改了

#include
using namespace std;
#define in Read()
#define re register
#define cross xor
#define point or
const int NNN=1e4+10;

struct node1{
	double x,y;
}dot[NNN];
struct node2{
	double x,y;
	friend inline double operator cross (const node2 &u,const node2 &v){//叉乘 
		return 1.0*u.x*v.y-1.0*u.y*v.x;
	}
	friend inline double operator point (const node2 &u,const node2 &v){//点乘
		return 1.0*u.x*v.x+1.0*u.y*v.y; 
	}
	friend inline node2 operator + (const node2 &u,const node2 &v){//向量加法 
		node2 ans;
		ans.x=u.x+v.x;
		ans.y=u.y+v.y;
		return ans;
	}
	friend inline node2 operator - (const node2 &u,const node2 &v){//向量减法 
		node2 ans;
		ans.x=u.x-v.x;
		ans.y=u.y-v.y;
		return ans;
	}
}vec[NNN];

inline node2 operator - (const node1 &a,const node1 &b){//a到b的向量(a减b)注意顺序 
	node2 ans;
	ans.x=a.x-b.x;
	ans.y=a.y-b.y;
	return ans;
}
inline node1 operator + (const node1 &a,const node2 &v){//已知起点和向量求终点
	node1 ans;
	ans.x=a.x+v.x;
	ans.y=a.y+v.y;
	return ans; 
}

int main(){
	
	return 0;
}

其他运算:

  • 向量的夹角:
    c o s < a ⃗ , b ⃗ > = a ⃗ ⋅ b ⃗ ∣ a ⃗ ∣ ∣ b ⃗ ∣ cos<\vec{a},\vec{b}>=\frac{\vec{a}·\vec{b}}{|\vec{a}||\vec{b}|} cos<a ,b >=a b a b

s i n < a ⃗ , b ⃗ > = ∣ a ⃗ × b ⃗ ∣ ∣ a ⃗ ∣ ∣ b ⃗ ∣ sin<\vec{a},\vec{b}>=\frac{|\vec{a}\times\vec{b}|}{|\vec{a}||\vec{b}|} sin<a ,b >=a b a ×b

  • 正余弦定理:自己推
  • 向量的旋转:用复平面推(用普通的旋转推也行,就是要注意一下正负)
  • 叉乘运用到的行列式:
    行列式的运算,每行任取一个不在同一列的数,相乘
    再依次排列他们的列数,其中的逆序对个数mod2就得到该积的符号(0为正,1为负)
  • 叉乘运用到右手法则
  • 向量的拐向用叉乘
  • 夹角的优劣用叉乘,夹角的钝锐用点乘(这里用到的思想就是必修四“看角的教材”里很好的思想了)

凸包

每个点(向量)要通过极角排一下序

POJ3348板子题
code

#include
#define in Read()
#define re register
#define swap std::swap
#define sort std::sort
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int NNN=2000+10;
const double eps=1e-6;
int num;
struct node{
	double x,y;
	node(){}
	node(double _x,double _y){x=_x,y=_y;}
	friend node operator - (const node u,const node v){return node(u.x-v.x,u.y-v.y);}
	friend node operator + (const node u,const node v){return node(u.x+v.x,u.y+v.y);}
	friend node operator * (const node u,const double a){return node(u.x*a,u.y*a);}
	friend node operator / (const node u,const double a){return node(u.x/a,u.y/a);}
	friend double operator * (const node u,const node v){return (u.x*v.x+u.y*v.y);}
	friend double operator ^ (const node u,const node v){return (u.x*v.y-u.y*v.x);}
	void PRINT_VEC(){printf("%.2lf %.2lf",x,y);}
	void SCAN_VEC(){scanf("%lf%lf",&x,&y);}
}vec[NNN],convec[NNN],O;
inline double dist(node u){return sqrt(u*u);}
inline double cros(node u,node v,node w){return (u-v)^(w-v);}//u←v→w
inline bool operator < (const node u,const node v){
	double res=cros(u,vec[1],v);
	return res==0?u.x<v.x:res>0;
}

int main(){
	O.x=0,O.y=0;
	int n=in,id=1;
	for(re int i=1;i<=n;++i){
		vec[i].SCAN_VEC();
		if(vec[i].x<vec[id].x||(fabs(vec[i].x-vec[id].x)<eps&&vec[i].y<vec[id].y))id=i;
	}
	swap(vec[1],vec[id]);
	sort(vec+2,vec+n+1);
//	for(re int i=1;i<=n;++i)vec[i].PRINT_VEC(),puts("");
	convec[++num]=vec[1];
	for(re int i=2;i<=n;++i){
		while(num>1&&cros(convec[num-1],convec[num],vec[i])>0)--num;
		convec[++num]=vec[i];
	}
//	for(re int i=1;i<=num;++i)convec[i].PRINT_VEC(),puts("");
	convec[0]=convec[num];
	double res=0;
	for(re int i=1;i<=num;++i)res+=cros(convec[i-1],O,convec[i]);
	printf("%d",(int)res/100);
	
	return 0;
}

旋转卡壳

看似O(N2)实际O(N)
是不是很妙?

很久以后再看代码,发现这真是一个旋转。。。

这个代码RE了,烦请各位大佬指正

//RE码,懒得改了 
#include
#include
#include
#define in Read()
#define re register
#define swap std::swap
#define sort std::sort
#define max std::max
inline int in{
	int i=0,f=1;char ch;
	while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(ch<='9'&&ch>='0')i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}
int n,id;
const int NNN=5000+10;
const double eps=1e-6;
int num;
struct node{
	double x,y;
	node(){}
	node(double _x,double _y){x=_x,y=_y;}
	friend node operator - (const node u,const node v){return node(u.x-v.x,u.y-v.y);}
	friend node operator + (const node u,const node v){return node(u.x+v.x,u.y+v.y);}
	friend node operator * (const node u,const double a){return node(u.x*a,u.y*a);}
	friend node operator / (const node u,const double a){return node(u.x/a,u.y/a);}
	friend double operator * (const node u,const node v){return (u.x*v.x+u.y*v.y);}
	friend double operator ^ (const node u,const node v){return (u.x*v.y-u.y*v.x);}
	void PRINT_VEC(){printf("%.2lf %.2lf",x,y);}
	void SCAN_VEC(){scanf("%lf%lf",&x,&y);}
}vec[NNN],convex[NNN],O;
inline double dist(node u){return sqrt(u*u);}
inline double cros(node u,node v,node w){return (u-v)^(w-v);}//u←v→w
inline bool operator < (const node u,const node v){
	double res=cros(u,vec[1],v);
	return res==0?u.x<v.x:res>0;
}
inline int nxt(int x){return x==n?1:x+1;}
inline int SOLVE(){
	int res=0;
	convex[n+1]=convex[1];
	for(re int i=1,j=3;i<=n;++i){
		while(nxt(j)!=i&&
		cros(convex[i],convex[i+1],convex[j])<=cros(convex[i],convex[i+1],convex[j+1]))j=nxt(j);
		res=max(res,(int)((convex[j]-convex[i])*(convex[j]-convex[i])));
		res=max(res,(int)((convex[j]-convex[i+1])*(convex[j]-convex[i+1])));
	}
	return res;
}

int main(){
	O.x=0,O.y=0;
	n=in,id=1;
	for(re int i=1;i<=n;++i){
		vec[i].SCAN_VEC();
		if(vec[i].x<vec[id].x||(fabs(vec[i].x-vec[id].x)<eps&&vec[i].y<vec[id].y))id=i;
	}
	swap(vec[1],vec[id]);
	sort(vec+2,vec+n+1);
	convex[++num]=vec[1];
	for(re int i=2;i<=n;++i){
		while(num>1&&cros(convex[num-1],convex[num],vec[i])>0)--num;
		convex[++num]=vec[i];
	}
	if(num==2){
		printf("%d",(int)((convex[1]-convex[2])*(convex[1]-convex[2])));
		return 0;
	}
	int ans=SOLVE();
	printf("%d",ans);
	return 0;
}

半平面交

处理面积交的问题
与线性规划很相似

polygon内部的所有点构成的点集叫做polygon的核
所有核的交集就是我们要求的面积

算法步骤:

  1. 极角排序,用atan2
    注意,如果你用的是逆时针围成多边形,下面要取的是向量的左侧
  2. 枚边
    能改变半平面交的就入栈
    让先前的栈内边无效的,把以前的边弹出去

你可能感兴趣的:(#,计算几何)