【几何】简单积累
由于几何问题一般是压轴题,对我来说过于复杂,而且这一块是交给队友了的,所以自己都没怎么做过。
为了应对蓝桥杯,和一些简单比赛,还是应该做几个几何水题,以免到时候遇到很水的题我都没有信心去做。
所以,下面的东西都很水,大神就不要浪费时间看了。
向量是基础:
可以方便使用加减乘除,可以有很多模板,而且能尽可能避免除法和三角函数,精度高,效率高。
对于向量的点积:
p*q*cos<o> 求同向还是异向;求投影;求出投影后用勾股定理求点到直线距离。
对于向量的叉积:
p*q*sin<o> 求面积;求顺时针方向还是逆时针方向;判断是否在半平面上。
(叉积表示有向面积,小于180度为正。)
题意:给定凸多边形的顶点(保证第一个是(0,0)),求按顺序输出顶点(保证无三点共线)。
思路:利用叉积排序,两个向量(顶点减(0,0))的叉积a^b,如果a^b>0,说明b在a的顺时针方向。(如果不是凸多边形不成立,如果三点共线不成立)。
//对于凸多边形,以0为起点的向量,叉积排序。 struct Vector { int x;int y;} node[maxn]; bool cmp(Vector a,Vector b) { return (a.x*b.y-a.y*b.x)>0; } sort(node+1,node+cnt,cmp); for(int i=0;i<cnt;i++) printf("(%d,%d)\n",node[i].x,node[i].y);
//对于凸多边形,以0为起点的向量,叉积排序。 #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1000000; struct Vector { int x;int y; bool friend operator < (Vector a,Vector b) { return a.x*b.y>b.x*a.y; } }; Vector node[maxn]; int main() { int cnt=0; while(~scanf("%d%d",&node[cnt].x,&node[cnt].y)) cnt++; sort(node+1,node+cnt); for(int i=0;i<cnt;i++) printf("(%d,%d)\n",node[i].x,node[i].y); return 0; }
HDU2036:改革春风吹满地 :
题意:按顺序给出多边形(有可能是凸多边形,有可能不是),求其面积。
思路:划分为若干个三角形,以第一个点为每个三角形的一个顶点避免重复,叉乘的1/2是面积,不加绝对值,避免出现凹多边形是面积算大了。
Vector friend operator -(Vector a,Vector b) { return Vector(a.x-b.x,a.y-b.y); } double friend operator ^(Vector w,Vector v) {//叉积 return w.x*v.y-v.x*w.y; } cin>>a.x>>a.y>>b.x>>b.y; for(i=3;i<=n;i++){ cin>>c.x>>c.y; ans+=0.5*((b-a)^(c-a)); b.x=c.x; b.y=c.y; }
#include<cstdio> #include<cstdlib> #include<iostream> #include<memory.h> #include<algorithm> #include<cmath> using namespace std; struct Vector { int x; int y; Vector(){} Vector(int xx,int yy):x(xx),y(yy){} Vector friend operator -(Vector a,Vector b) { return Vector(a.x-b.x,a.y-b.y); } double friend operator ^(Vector w,Vector v) {//叉积 return w.x*v.y-v.x*w.y; } }; Vector a,b,c; int main() { int i,n; double ans,temp; while(~scanf("%d",&n)){ if(n==0) return 0; ans=0; cin>>a.x>>a.y>>b.x>>b.y; for(i=3;i<=n;i++){ cin>>c.x>>c.y; ans+=0.5*((b-a)^(c-a)); b.x=c.x; b.y=c.y; } printf("%.1lf\n",ans); } return 0; }
POJ1569:Myacm Triangles
题意:很少的点,求三个点,组成的三角形面积最大,且其他点不在三角形边上或者内部。
思路:枚举点,然后检查其他点是否在内部或者边上。我知道的检查方式有两种(可能还有很多种):
1,经过点画一条直线,与多边形的交点个数为偶则在内部。
2,叉积判断,对于凸多边形,用逆时针表示向量方向,如果一个点在所有边的左边,则它在多边形内部。
这里用的第二种,逆时针表示可以通过叉积排序做到(见上面第一题)。
int Vector_S(int a,int b,int c) { int tmp=(node[b].x-node[a].x)*(node[c].y-node[a].y)-(node[c].x-node[a].x)*(node[b].y-node[a].y);//叉积 if(tmp==0) return -1;//三点共线,不构成三角形 else if(tmp<0) swap(b,c),tmp=-tmp;//叉积排序,保证是逆时针的三角形。 for(int i=1;i<=N;i++){ if(i==a||i==b||i==c) continue; bool Flag=true; //如果有一个在右边则不是在多边形内部。 if((node[b].x-node[a].x)*(node[i].y-node[a].y)<(node[i].x-node[a].x)*(node[b].y-node[a].y)) Flag=false; if((node[c].x-node[b].x)*(node[i].y-node[b].y)<(node[i].x-node[b].x)*(node[c].y-node[b].y)) Flag=false; if((node[a].x-node[c].x)*(node[i].y-node[c].y)<(node[i].x-node[c].x)*(node[a].y-node[c].y)) Flag=false; if(Flag) return -1; } return tmp; }
#include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; struct Point{ char id[2]; int x,y; Point(){} }node[20]; int N; int Vector_S(int a,int b,int c) { int tmp=(node[b].x-node[a].x)*(node[c].y-node[a].y)-(node[c].x-node[a].x)*(node[b].y-node[a].y); if(tmp==0) return -1; else if(tmp<0) swap(b,c),tmp=-tmp;//保证是逆时针的三角形。 for(int i=1;i<=N;i++){ if(i==a||i==b||i==c) continue; bool Flag=true; //如果有一个在右边则不是在多边形内部。 if((node[b].x-node[a].x)*(node[i].y-node[a].y)<(node[i].x-node[a].x)*(node[b].y-node[a].y)) Flag=false; if((node[c].x-node[b].x)*(node[i].y-node[b].y)<(node[i].x-node[b].x)*(node[c].y-node[b].y)) Flag=false; if((node[a].x-node[c].x)*(node[i].y-node[c].y)<(node[i].x-node[c].x)*(node[a].y-node[c].y)) Flag=false; if(Flag) return -1; } return tmp; } int main() { int i,j,k,a,b,c,Max; while(~scanf("%d",&N)){ Max=-1; if(N==0) break; for(i=1;i<=N;i++) scanf("%s%d%d",node[i].id,&node[i].x,&node[i].y); for(i=1;i<=N;i++) for(j=1;j<i;j++) for(k=1;k<j;k++){ int tmp=Vector_S(i,j,k); if(tmp>Max) Max=tmp,a=i,b=j,c=k; } if(node[a].id[0]>node[b].id[0]) swap(a,b); if(node[b].id[0]>node[c].id[0]) swap(b,c); if(node[a].id[0]>node[b].id[0]) swap(a,b); printf("%c%c%c\n",node[a].id[0],node[b].id[0],node[c].id[0]); } return 0; }
POJ3304:Segments
题意:已知N条线段,问是否存在直线X,使得这些线段在直线上的投影有公共点。
思路:转化一下,就是问是是否存在直线X,使得X的垂线L与每个线段都有公共点。如果存在L,那么把其中一些满足条件的L平移,
可以使得它结果两个已知线段的端点。
注意一下:需要判断是否两点重合,开始我用的方法是fabs(x1-x2)<eps,fabs(y1-y2)<eps则重合,改为dist<=eps则重合就AC了。
bool check(Cpoint w, Cpoint v) { if(dist(w,v)<=eps) return false; //判断两点重合,开始用点直接比较WA了。 Vector base(w.x-v.x,w.y-v.y); for(int i=1;i<=N;i++){ Vector e(L[i].a.x-v.x,L[i].a.y-v.y); Vector p(L[i].b.x-v.x,L[i].b.y-v.y); if((base^e)*(base^p)>eps) return false; } return true; } bool find() { for(int i=1;i<=N;i++) for(int j=1;j<=N;j++){ if(check(L[i].a,L[j].a)) return true; if(check(L[i].a,L[j].b)) return true; if(check(L[i].b,L[j].a)) return true; if(check(L[i].b,L[j].b)) return true; } return false; }
#include<cmath> #include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int maxn=110; const double eps=1e-8; int N; struct Cpoint { double x,y; Cpoint(){} Cpoint(double xx,double yy):x(xx),y(yy){} }; struct Cline { Cpoint a,b; Cline(){} Cline(Cpoint aa,Cpoint bb):a(aa),b(bb){} }; struct Vector { double x,y; Vector(){} Vector(double xx,double yy):x(xx),y(yy){} double friend operator ^(Vector a,Vector b){ return a.x*b.y-b.x*a.y; } }; double dist(Cpoint a,Cpoint b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } Cline L[maxn]; bool check(Cpoint w, Cpoint v) { if(dist(w,v)<=eps) return false;// 判断两点重合,开始用点直接比较WA了。 Vector base(w.x-v.x,w.y-v.y); for(int i=1;i<=N;i++){ Vector e(L[i].a.x-v.x,L[i].a.y-v.y); Vector p(L[i].b.x-v.x,L[i].b.y-v.y); if((base^e)*(base^p)>eps) return false; } return true; } bool find() { for(int i=1;i<=N;i++) for(int j=1;j<=N;j++){ if(check(L[i].a,L[j].a)) return true; if(check(L[i].a,L[j].b)) return true; if(check(L[i].b,L[j].a)) return true; if(check(L[i].b,L[j].b)) return true; } return false; } int main() { int T; double x,y,xx,yy; scanf("%d",&T); while(T--){ scanf("%d",&N); for(int i=1;i<=N;i++){ scanf("%lf%lf%lf%lf",&x,&y,&xx,&yy); L[i]=Cline(Cpoint(x,y),Cpoint(xx,yy)); } if(find()) printf("Yes!\n"); else printf("No!\n"); } return 0; }
POJ1066:Treasure Hunt
题意:一些线段把100*100的正方形分割成一些多边形,现在要求从正方形边界到达某一点P,每次只能穿过多边形的边的中点,求至少穿过多少边。
思路:开始我一位每次只能穿过每条线段的中点,那么这样只有最多N个点,可以用最短路算法求。 但是是多边形的中点,这个限制可以忽略,所以直接求交点就行。
枚举起点S,连接P,求交点个数,但是需要完全相交,即在边缘的不算,即起点S也是不算的,表示我们枚举是起点S,其实代表的是S附近的一段线段,所以我们枚举正方形上的点,其实代表了100*100的正方形。
注意:精度,线段完全相交我们使用叉积判断。
Cpoint P(x,y); for(int i=1;i<=N;i++){ //枚举线段 Cpoint tmp1=L[i].a; ans=min(ans,find_intersect(Cline(P,tmp1))); Cpoint tmp2=L[i].b; ans=min(ans,find_intersect(Cline(P,tmp2))); }
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=100; const double eps=1e-8; int N,ans; struct Cpoint { double x,y; Cpoint(){} Cpoint(double xx,double yy):x(xx),y(yy){} }; struct Cvector { double x,y; Cvector(){} Cvector(double xx,double yy):x(xx),y(yy){} double friend operator ^(Cvector w,Cvector v)//线段完全相交 { return w.x*v.y-v.x*w.y; } }; struct Cline { Cpoint a,b; Cline(){} Cline(Cpoint aa,Cpoint bb):a(aa),b(bb){} bool friend operator *(Cline w,Cline v)//线段完全相交 { Cvector x1(w.b.x-w.a.x,w.b.y-w.a.y); Cvector x2(v.b.x-w.a.x,v.b.y-w.a.y); Cvector x3(v.a.x-w.a.x,v.a.y-w.a.y); if((x1^x2)*(x1^x3)>=-eps) return false; x1=Cvector(v.b.x-v.a.x,v.b.y-v.a.y); x2=Cvector(w.b.x-v.a.x,w.b.y-v.a.y); x3=Cvector(w.a.x-v.a.x,w.a.y-v.a.y); if((x1^x2)*(x1^x3)>=-eps) return false; return true; } }; Cline L[maxn]; int find_intersect(Cline x) { int res=0; for(int i=1;i<=N;i++){ if(x*L[i]) res++; } return res; } int main() { double x,y,z,w; scanf("%d",&N); ans=N; for(int i=1;i<=N;i++){ scanf("%lf%lf%lf%lf",&x,&y,&z,&w); L[i]=Cline(Cpoint(x,y),Cpoint(z,w)); } scanf("%lf%lf",&x,&y); Cpoint P(x,y); for(int i=1;i<=N;i++){ //枚举线段 Cpoint tmp1=L[i].a; ans=min(ans,find_intersect(Cline(P,tmp1))); Cpoint tmp2=L[i].b; ans=min(ans,find_intersect(Cline(P,tmp2))); } printf("Number of doors = %d\n",ans+1); return 0; }
POJ1113: Wall
题意:给定N个点,求用一个多边形把这些点包括进去,且每个点到多边形的距离都大于等于L。
思路:
先不考虑L这个条件,因为两点之间,直线最短,所以对于凹进去的部分,我们肯定有最短的直线可以包含它,可以忽略,所以是求凸包。
然后考虑L,对于求出的凸多边形,对于它的顶点X,可以证明每个X附近需要增加一定的圆弧来保证顶点到圆弧的距离大于等于L,
所有X的圆弧角度之和为Pi,所以ans=凸包+2*Pi*L。
sort(P+1,P+N+1); //得到“原点 ” sort(P+2,P+N+1,cmp); //得到积角序 int q[maxn],top=3; q[1]=1; q[2]=2; q[3]=3; for(int i=4;i<=N;i++){ while(top>1&&Sign((P[q[top]]-P[q[top-1]])^(P[i]-P[q[top]]))<=0) top--; q[++top]=i; } for(int i=1;i<top;i++) res+=dist(P[q[i]],P[q[i+1]]); res=res+dist(P[q[top]],P[1])+2.0*pi*L; return res;
#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1010; const double pi=acos(-1.0); const double eps=1e-6; struct Cpoint { double x,y; Cpoint(){} Cpoint(double xx,double yy):x(xx),y(yy){} Cpoint friend operator -(Cpoint a,Cpoint b){ return Cpoint(a.x-b.x, a.y-b.y); } double friend operator ^(Cpoint a,Cpoint b){ return a.x*b.y-b.x*a.y; } bool friend operator <(Cpoint a,Cpoint b){ if(a.y==b.y) return a.x<b.x; return a.y<b.y; } }; double dist(Cpoint a,Cpoint b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } int Sign(double x) { if(x>=-eps&&x<=eps) return 0; if(x>eps) return 1; return -1; } int N,L; Cpoint P[maxn]; bool cmp(Cpoint a,Cpoint b) { int s=Sign((a-P[1])^(b-P[1])); if(s>0||(s==0&&dist(a,P[1])<dist(b,P[1]))) return true; return false; } double Graham() //如果N<3还得讨论一下。 { double res=0; sort(P+1,P+N+1); //得到“原点 ” sort(P+2,P+N+1,cmp); //得到积角序 int q[maxn],top=3; q[1]=1; q[2]=2; q[3]=3; for(int i=4;i<=N;i++){ while(top>1&&Sign((P[q[top]]-P[q[top-1]])^(P[i]-P[q[top]]))<=0) top--; q[++top]=i; } for(int i=1;i<top;i++) res+=dist(P[q[i]],P[q[i+1]]); res=res+dist(P[q[top]],P[1])+2.0*pi*L; return res; } int main() { while(~scanf("%d%d",&N,&L)){ for(int i=1;i<=N;i++) scanf("%lf%lf",&P[i].x,&P[i].y); printf("%d\n",(int)(Graham()+0.5)); }return 0; }
POJ1228:Grandpa's Estate
题意:给定一些点,问是否能确定一个唯一的凸包。
思路:求出凸包后,每条边上若至少有3个点,则可以确定。
if(N<6) return false;//肯定不构成凸包 sort(P+1,P+N+1); //得到“原点 ” sort(P+2,P+N+1,cmp); //得到积角序 int q[maxn],top=2; q[1]=1; q[2]=2; //q[3]=3; for(int i=3;i<=N;i++){ while(top>1&&Sign((P[q[top]]-P[q[top-1]])^(P[i]-P[q[top]]))<=0) top--; q[++top]=i; } if(top<3) return false; //不构成凸包 for(int i=1;i<top;i++) if(find(P[q[i]],P[q[i+1]])<3) return false; if(find(P[q[top]],P[q[1]])<3) return false; return true;
#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1010; const double pi=acos(-1.0); const double eps=1e-6; struct Cpoint { double x,y; Cpoint(){} Cpoint(double xx,double yy):x(xx),y(yy){} Cpoint friend operator -(Cpoint a,Cpoint b){ return Cpoint(a.x-b.x, a.y-b.y); } double friend operator ^(Cpoint a,Cpoint b){ return a.x*b.y-b.x*a.y; } bool friend operator <(Cpoint a,Cpoint b){ if(a.y==b.y) return a.x<b.x; return a.y<b.y; } }; double dist(Cpoint a,Cpoint b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } int Sign(double x) { if(x>=-eps&&x<=eps) return 0; if(x>eps) return 1; return -1; } int N; Cpoint P[maxn]; bool cmp(Cpoint a,Cpoint b) { int s=Sign((a-P[1])^(b-P[1])); if(s>0||(s==0&&dist(a,P[1])<dist(b,P[1]))) return true; return false; } int find(Cpoint a,Cpoint b) { int res=0; for(int i=1;i<=N;i++){ if(Sign((P[i].x-a.x)*(P[i].x-b.x))>0) continue; if(Sign((P[i].y-a.y)*(P[i].y-b.y))>0) continue; if(Sign((b-a)^(P[i]-a))==0) res++; } return res; } bool Graham() { if(N<6) return false;//肯定不构成凸包 sort(P+1,P+N+1); //得到“原点 ” sort(P+2,P+N+1,cmp); //得到积角序 int q[maxn],top=2; q[1]=1; q[2]=2; //q[3]=3; for(int i=3;i<=N;i++){ while(top>1&&Sign((P[q[top]]-P[q[top-1]])^(P[i]-P[q[top]]))<=0) top--; q[++top]=i; } if(top<3) return false; //不构成凸包 for(int i=1;i<top;i++) if(find(P[q[i]],P[q[i+1]])<3) return false; if(find(P[q[top]],P[q[1]])<3) return false; return true; } 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); if(Graham()) printf("YES\n"); else printf("NO\n"); }return 0; }