精度设置:eps判0
double const EPS = 1E-6;
#define is0(x) ( -EPS <= (x) && (x) <= EPS )
整数表示点坐标:
//点
struct point_t{
int x;
int y;
};
点积
- 点积隐含了两个向量夹角的余弦值。
- 点积为0,两向量垂直;为正,锐角;为负,钝角。仿照叉积
点积的实现如下:
//向量的点积,表示OA·OB
int dot(point_t const&O,point_t const& A,point_t const& B){
int xoa = A.x – O.x;
int yoa = A.y – O.y;
int xob = B.x – O.x;
int yob = B.y – O.y;
return xoa * xob + yoa * yob;
}
叉积
- 二维叉积代表2个向量张成的平行四边形的“有向”面积
- 向量a与向量b的叉积为正,则a到b是逆时针方向;反之则为顺时针。
使用3个点计算叉积
//向量的叉积,表示OA×OB
int cross(point_t const&O,point_t const&A,point_t const&B)
{
int xoa=A.x-O.x;
int yoa=A.y-O.y;
int xob=B.x-O.x;
int yob=B.y-O.y;
return xoa*yob-yoa*xob;
}
叉积判断两个向量的幅角大小
- 求向量a(x,y)的幅角可以调用atan(y,x),(浮点数有精度误差)还可用叉积算
- 首先判断向量a,b所在象限,不在同一象限则大小很明显,否则计算它们的叉积,由正负可以判断大小。
判断两线段是否相交
线段数据结构:
//直线SE
struct lineseg_t{
point_t s;
point_t e;
};
判断两线段AB与CD是否相交,两步:
1、排斥实验:即存在一条直线l,AB与CD在其上的投影不相交,则它们一定不相交。但不满足排斥实验不一定相交。
可以将y轴作为l,则比较两条线段最大的x即可。
2、跨立实验:若两条直线相交,则有CD跨过了AB,且AB跨过CD。即CD在AB的两侧,AB在CD的两侧。
下图:判断CD跨过了AB,即CD在AB的两侧:即AD到AB方向与AB到AC方向,即AD×AB与AB×AC同号
判断AB没跨过CD:即CA×CD与CD×CB不同号,则不想交
//判断线段AB与CD是否相交,true表示相交
bool isInter(point_t const&A,point_t const&B, point_t const&C,point_t const&D)
{
return max(A.x, B.x) >= min(C.x, D.x) //排斥
&& max(A.y, B.y) >= min(C.y, D.y) //排斥
&& max(C.x, D.x) >= min(A.x, B.x) //排斥
&& max(C.y, D.y) >= min(A.y, B.y) //排斥
&& cross(A, C, B) * cross(A, B, D) >= 0 //跨立
&& cross(C, A, D) * cross(C, D, B) >= 0 ; //跨立
}
//判断直线AB与线段CD是否相交
bool isIntersect(point_t const&A,point_t const&B, point_t const&C,point_t const&D)
{
return cross(A,B,C)*cross(A,B,D)>eps;//true表示不相交
}
直线的位置关系(平面几何)
所用直线数据结构:
//直线ax+by+c=0;
struct line_t
{
int a,b,c;//表示ax+by+c=0,一般使得a、b、c互质
}
题目一般不会给直线方程,而是给定两个点,所以可以用叉积计算出a,b,c。
a = y1 - y2, b = x2 - x1, c = x1y2 - x2y1
求出两条直线的方程后,通过a,b,c的关系就很容易判断直线关系了。
令:x = b1c2 - b2c1, y = a2c1 - a1c2, t = a1b2 - a2b1,则:
重合:x=y=t=0
平行:t=0
否则相交:两直线相交且交点为(x/t, y/t)
实例
叉积:poj2318:TOYS
theme:给定一个大箱子的左上、右下坐标、隔板数、玩具数。给出n个隔板的横坐标,(两个纵坐标同隔板),保证不会相交,且按从左到右顺序给出。给出m个玩具的横纵坐标,问最终每个区域的玩具数。0 <n, m <= 5000
solution:对于每一个点,枚举每一个区域判断它是否在其内部即可。每个区域由相邻两条直线组成,所以只需要写个函数判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界。
//判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界
#include<iostream>
#include<cstdio>
#include<algorithm>
typedef long long LL;
#define inf 0x3f3f3f3f
using namespace std;
#define far(i,s,n) for(int i=s;i<n;++i)
//点
struct point_t{
int x;
int y;
};
//叉积
int cross(point_t const&O,point_t const&A,point_t const&B)
{
int xoa=A.x-O.x;
int yoa=A.y-O.y;
int xob=B.x-O.x;
int yob=B.y-O.y;
return xoa*yob-yoa*xob;
}
//判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界
bool inside(point_t const&O,point_t const& A,point_t const& B,point_t const& C,point_t const& D)
{
if(O.x<min(A.x,B.x)||O.x>max(C.x,D.x))return false;
if(O.y<B.y||O.y>A.y)return false;
int crossl=cross(B,O,A);
int crossr=cross(D,C,O);
return (crossl>=0)&&(crossr>=0);
}
int n,m,xlm,ylm,xrm,yrm;
int U[5050],L[5050];
int cnt[5050];
int main()
{
while(scanf("%d",&n)&&n)
{
scanf("%d%d%d%d%d",&m,&xlm,&ylm,&xrm,&yrm);
fill(cnt,cnt+n+2,0);
far(i,1,n+1)
scanf("%d%d",&U[i],&L[i]);
far(i,0,m)
{
point_t toy;
scanf("%d%d",&toy.x,&toy.y);
//判断是否在0号区域
if(inside(toy,point_t{xlm,ylm},point_t{xlm,yrm},point_t{U[1],ylm},point_t{L[1],yrm}))
++cnt[0];
else if(inside(toy,point_t{U[n],ylm},point_t{L[n],yrm},point_t{xrm,ylm},point_t{xrm,yrm}))
++cnt[n];
else
{
far(j,1,n)
{
if(inside(toy,point_t{U[j],ylm},point_t{L[j],yrm},point_t{U[j+1],ylm},point_t{L[j+1],yrm}))
{
++cnt[j];
break;
}
}
}
}
far(i,0,n+1)
printf("%d: %d\n",i,cnt[i]);
puts("");
}
}
叉积:poj3304:Segments
theme:给定n条线段,问是否存在一条直线,使得所有n条直线投影到该直线的区域有公共点。n ≤ 100
solution:如果存在这样一条直线,则过公共点坐该直线的垂线一定与所有n条线段相交,所以问题转化为如果有一条直线与所有线段相交,则与该直线垂直的直线即为所求。所以我们的目标是判断欲呕恶u一条直线与所有线段相交。首先如果存在,则经过n条直线2n个顶点任意两点的直线一定是(平移这条直线一定可以一到与至少两个顶点相交,否则这条直线也不会和所有直线相交。)所以我们只需枚举2n个顶点即可,对于只有1条线段等的情况,可能就是该直线与所有直线相交,所以一条线段的两个端点也要考虑。注意数据可能出现十分相近的点,这时应该跳过
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double eps=1e-8;
//点
struct point_t
{
double x;
double y;
};
//叉积
double cross(point_t const&O,point_t const&A,point_t const&B)
{
double xoa=A.x-O.x;
double yoa=A.y-O.y;
double xob=B.x-O.x;
double yob=B.y-O.y;
return xoa*yob-yoa*xob;
}
//判断直线AB与CD是否相交,true表示不相交
bool isIntersect(point_t const&A,point_t const&B, point_t const&C,point_t const&D){
return cross(A,B,C)*cross(A,B,D)>eps;
}
double X[220],Y[220];
int n;
bool check(point_t A,point_t B)
{
if(fabs(A.x-B.x)<eps&&fabs(A.y-B.y)<eps)
return false;
for(int i=1;i<=n;++i)
{
point_t C=point_t{X[i],Y[i]};
point_t D=point_t{X[i+n],Y[i+n]};
if(isIntersect(A,B,C,D))
return false;
}
return true;
}
int main()
{
int Case;
cin>>Case;
while(Case--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%lf%lf%lf%lf",&X[i],&Y[i],&X[i+n],&Y[i+n]);
int flag=0;
for(int i=1;i<=2*n;++i)
{
if(flag)
break;
for(int j=i+1;j<=2*n;++j)
{
point_t A=point_t{X[i],Y[i]};
point_t B=point_t{X[j],Y[j]};
if(check(A,B))
{
flag=1;
break;
}
}
}
if(flag)
puts("Yes!");
else
puts("No!");
}
}
直线位置关系:poj1269:Intersecting Lines
theme:判断两条直线的位置关系
solution:求出两条直线的a,b,c
//theme:判断两条直线的位置关系
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
//直线ax+by+c=0;
struct line_t
{
int a,b,c;//表示ax+by+c=0,一般使得a、b、c互质
};
int check(line_t l1,line_t l2,double &ix,double &iy)
{
int x=l1.b*l2.c-l2.b*l1.c;
int y=l2.a*l1.c-l1.a*l2.c;
int t=l1.a*l2.b-l2.a*l1.b;
if(x==0&&y==0&&t==0)
return 1;
if(t==0)
return 2;
ix=1.0*x/t;
iy=1.0*y/t;
return 3;
}
int main()
{
int n;
cin>>n;
puts("INTERSECTING LINES OUTPUT");
while(n--)
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
line_t l1=line_t{y1-y2,x2-x1,x1*y2-x2*y1};
int x3,x4,y3,y4;
scanf("%d%d%d%d",&x3,&y3,&x4,&y4);
line_t l2=line_t{y3-y4,x4-x3,x3*y4-x4*y3};
double x=0,y=0;
int res=check(l1,l2,x,y);
if(res==1)
puts("LINE");
else if(res==2)
puts("NONE");
else
printf("POINT %.2f %.2f\n",x,y);
}
puts("END OF OUTPUT");
}
Pick面积公式
- 平面上以格子点为顶点的简单多边形的面积=边界覆盖点数/2 + 内部覆盖点数 -1
- 以格子点为顶点的线段,覆盖的点的个数为gcd(dx,dy),其中,dx、dy分别为线段横向占的点数和纵向占的点数。如果dx或dy为0,则覆盖的点数为dy或dx。
- 顶点是整数就暗示在网格点上,一般面积都是用叉积算,再用pick公式求出内部覆盖点数
将平面中的点按逆时针排序
即按叉积排序
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
struct Vector {
double x, y;
Vector(int x, int y):x(x), y(y){}
Vector(){}
double operator ^ (const Vector &v) const {
return x * v.y - y * v.x; //叉乘
}
};
#define Point Vector
Vector operator - (const Point &p1, const Point& p2) {
return Vector(p1.x - p2.x, p1.y - p2.y);
}
bool operator < (const Point &p1, const Point &p2) {
return (Vector(p2 - Point(0, 0)) ^ Vector(p1 - Point(0, 0))) > 0;
}
Point ps[60];
int main()
{
int n(0);
while(cin >> ps[n].x >> ps[n].y) {
++n;
}
sort(ps + 1, ps + n);//以p0作为第一个点逆时针排序
cout << "(0,0)" << endl;
for (int i = n - 1; i > 0; --i) {
cout << "(" << ps[i].x << "," << ps[i].y << ")" << endl;
}
return 0;
}
poj1265:Area
theme:逆时针给出多边形m条边沿x、y轴方向的长度,多边形的顶点都在网格上,求多边形边所覆盖的网格点个数、内部覆盖的网格点个数、多边形的面积。
solution:看到顶点在网格上,求多边形面积就应该想到pick公式,求多边形面积可以用叉积
求出面积后用gcd(dx,dy)可以求出边所覆盖的网格点个数,再用pick公式就可以求出内部覆盖的点的个数。
//theme:逆时针给出多边形m条边沿x、y轴方向的长度,多边形的顶点都在网格上,求多边形边所覆盖的网格点个数、内部覆盖的网格点个数、多边形的面积。
/*
注意这题因为给的是边的距离,我们自己假设第一个点为起点,所以求面积的时候就忽略了最后一个点与第一个点的叉积*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define far(i,s,t) for(int i=s;i<t;++i)
using namespace std;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
int Case;
cin>>Case;
int now=0;
while(Case--)
{
++now;
int n;
scanf("%d",&n);
int dx,dy,area=0,inside,edge=0;
int x1=0,y1=0,x2,y2;
far(i,0,n)//起点假设为原点就没必要算起点与最后一个点的叉积了
{
scanf("%d%d",&dx,&dy);
edge+=abs(gcd(dx,dy));
x2=x1+dx,y2=y1+dy;
area+=x1*y2-x2*y1;
x1=x2,y1=y2;
}
if(area<0)
area=-area;
inside=(area-edge+2)/2;
printf("Scenario #%d:\n%d %d %.1f\n\n",now,inside,edge,area/2.0);
}
}
任意多边形面积求法
任意一个多边形的面积=按顺序求相邻两个点与原点组成的向量的叉积之和/2。由于叉积是有方向的,所以最终结果取绝对值即可
其实就是拆分成多个三角形求和再减,与源点坐标的选取无关.
注意最后还要算最后一个点与第一个点的叉积
给定n个单位圆,求一条直线最多能穿过多少个圆?
//给定n个单位圆,求一条直线最多能穿过多少个圆?
#include <iostream>
#include <cmath>
double const PI = acos(-1);
using namespace std;
struct Point{
double x,y;
};
typedef Point Vector;
Vector operator + (Vector A,Vector B){
return Vector{A.x+B.x,A.y+B.y};
}
Vector operator - (Vector A,Vector B){
return Vector{A.x-B.x,A.y-B.y};
}
Vector operator * (Vector A,double p){
return Vector{A.x*p,A.y*p};
}
Vector operator / (Vector A,double p){
return Vector{A.x/p,A.y/p};
}
bool operator < (const Point& a,const Point& b){
return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
struct Line{
Vector v;
Point p;
};
struct Circle{
Point c;
double r;
Point getPoint(double a){
return Point{c.x+cos(a)*r,c.y+sin(a)*r};
}
};
const double eps = 1e-6;
int dcmp(double x){
if(fabs(x)<eps) return 0;
else return x<0?-1:1;
}
bool operator == (const Point& a,const Point& b){
return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}
int getLineCircleIntersection(Line L,Circle C){
double a = L.v.x,b=L.p.x - C.c.x,c=L.v.y,d=L.p.y-C.c.y;
double e=a*a + c*c,f=2*(a*b+c*d),g=b*b+d*d-C.r*C.r;
double delta = f*f - 4*e*g;
if(dcmp(delta)<0) return 0;//相离
if(dcmp(delta)==0) return 1;//相切
return 2;//相交
}
int getTangents(Circle A, Circle B, Point *a, Point *b){
if (A.c==B.c){
a[0]=A.c;
b[0]=A.getPoint(0);
return 1;
}
int cnt = 0;
double d2 = (A.c.x-B.c.x)*(A.c.x-B.c.x) + (A.c.y-B.c.y)*(A.c.y-B.c.y);
double rDiff = 0.0;
double rSum = 2.0;
double base = atan2(B.c.y - A.c.y, B.c.x - A.c.x);
double ang = acos(0);
a[cnt] = A.getPoint(base + ang);
b[cnt] = B.getPoint(base + ang);
++cnt;
a[cnt] = A.getPoint(base - ang);
b[cnt] = B.getPoint(base - ang);
++cnt;
if(dcmp(d2 - 4.0) == 0){
a[cnt] = A.getPoint(base);
Vector t_v = A.c-B.c;
if(dcmp(t_v.y)==0){
b[cnt]= Point{a[cnt].x,a[cnt].y+1.0};
}
else{
b[cnt] = Point{a[cnt].x+1.0,a[cnt].y-t_v.x/t_v.y};
}
++cnt;
}
else if(dcmp(d2 - 4.0)>0){
double ang2 = acos((2.0)/sqrt(d2));
a[cnt] = A.getPoint(base + ang2);b[cnt] = B.getPoint(PI+base+ang2);++cnt;
a[cnt] = A.getPoint(base - ang2);b[cnt] = B.getPoint(PI+base-ang2);++cnt;
}
return cnt;
}
const int maxn = 105;
int n;
Point poi[maxn];
Circle cir[maxn];
int main(){
cin>>n;
double x,y;
for(int i=1;i<=n;i++){
cin>>x>>y;
poi[i].x=x;
poi[i].y=y;
cir[i].c.x=x;
cir[i].c.y=y;
cir[i].r=1.0;
}
Line l;
int ans=2,cnt,ct;
Point a[10],b[10];//存两圆间切线经过的两点
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
ct=getTangents(cir[i],cir[j],a,b);
for(int d=0;d<ct;d++){
cnt=0;
l.v=(a[d]-b[d]);
l.v=l.v/sqrt(l.v.x*l.v.x + l.v.y*l.v.y);
l.p=b[d];
for(int k=1;k<=n;k++){
if(getLineCircleIntersection(l,cir[k])) cnt++;
}
ans=max(ans,cnt);
}
}
}
if(n==1) ans=1;
cout<<ans;
return 0;
}
求两个多边形并的面积
hdu3060:Area2
theme:给定两个多边形(凸或凹都可以),求它们交的面积。
//theme:给定两个多边形(凸或凹都可以),求它们交的面积。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 550;
const double eps = 1e-8;
int dcmp(double x)
{
if(x > eps) return 1;
return x < -eps ? -1 : 0;
}
struct Point
{
double x, y;
};
double cross(Point a,Point b,Point c) ///叉积
{
return (a.x-c.x)*(b.y-c.y)-(b.x-c.x)*(a.y-c.y);
}
Point intersection(Point a,Point b,Point c,Point d)
{
Point p = a;
double t =((a.x-c.x)*(c.y-d.y)-(a.y-c.y)*(c.x-d.x))/((a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x));
p.x +=(b.x-a.x)*t;
p.y +=(b.y-a.y)*t;
return p;
}
//计算多边形面积
double PolygonArea(Point p[], int n)
{
if(n < 3) return 0.0;
double s = p[0].y * (p[n - 1].x - p[1].x);
p[n] = p[0];
for(int i = 1; i < n; ++ i)
s += p[i].y * (p[i - 1].x - p[i + 1].x);
return fabs(s * 0.5);
}
double CPIA(Point a[], Point b[], int na, int nb)//ConvexPolygonIntersectArea
{
Point p[20], tmp[20];
int tn, sflag, eflag;
a[na] = a[0], b[nb] = b[0];
memcpy(p,b,sizeof(Point)*(nb + 1));
for(int i = 0; i < na && nb > 2; i++)
{
sflag = dcmp(cross(a[i + 1], p[0],a[i]));
for(int j = tn = 0; j < nb; j++, sflag = eflag)
{
if(sflag>=0) tmp[tn++] = p[j];
eflag = dcmp(cross(a[i + 1], p[j + 1],a[i]));
if((sflag ^ eflag) == -2)
tmp[tn++] = intersection(a[i], a[i + 1], p[j], p[j + 1]); ///求交点
}
memcpy(p, tmp, sizeof(Point) * tn);
nb = tn, p[nb] = p[0];
}
if(nb < 3) return 0.0;
return PolygonArea(p, nb);
}
double SPIA(Point a[], Point b[], int na, int nb)///SimplePolygonIntersectArea 调用此函数
{
int i, j;
Point t1[4], t2[4];
double res = 0, num1, num2;
a[na] = t1[0] = a[0], b[nb] = t2[0] = b[0];
for(i = 2; i < na; i++)
{
t1[1] = a[i-1], t1[2] = a[i];
num1 = dcmp(cross(t1[1], t1[2],t1[0]));
if(num1 < 0) swap(t1[1], t1[2]);
for(j = 2; j < nb; j++)
{
t2[1] = b[j - 1], t2[2] = b[j];
num2 = dcmp(cross(t2[1], t2[2],t2[0]));
if(num2 < 0) swap(t2[1], t2[2]);
res += CPIA(t1, t2, 3, 3) * num1 * num2;
}
}
return PolygonArea(a, na) + PolygonArea(b, nb) - res;//res为两凸多边形的交的面积
}
Point p1[maxn], p2[maxn];
int n1, n2;
int main()
{
while(scanf("%d%d",&n1,&n2)!=EOF)
{
for(int i = 0; i < n1; i++) scanf("%lf%lf", &p1[i].x, &p1[i].y);
for(int i = 0; i < n2; i++) scanf("%lf%lf", &p2[i].x, &p2[i].y);
double Area = SPIA(p1, p2, n1, n2);
Area=PolygonArea(p1,n1)+PolygonArea(p2,n2)-Area;
printf("%.2f\n",Area);
}
return 0;
}