HDU2892求多边形与圆相交面积
Problem Description
小白最近被空军特招为飞行员,参与一项实战演习。演习的内容是轰炸某个岛屿。。。
作为一名优秀的飞行员,任务是必须要完成的,当然,凭借小白出色的操作,顺利地将炸弹投到了岛上某个位置,可是长官更关心的是,小白投掷的炸弹到底摧毁了岛上多大的区域?
岛是一个不规则的多边形,而炸弹的爆炸半径为R。
小白只知道自己在(x,y,h)的空间坐标处以(x1,y1,0)的速度水平飞行时投下的炸弹,请你计算出小白所摧毁的岛屿的面积有多大. 重力加速度G = 10.
Input
首先输入三个数代表小白投弹的坐标(x,y,h);
然后输入两个数代表飞机当前的速度(x1, y1);
接着输入炸弹的爆炸半径R;
再输入一个数n,代表岛屿由n个点组成;
最后输入n行,每行输入一个(x’,y’)坐标,代表岛屿的顶点(按顺势针或者逆时针给出)。(3<= n < 100000)
Output
输出一个两位小数,表示实际轰炸到的岛屿的面积。
先根据简单的初中物理牛顿力学的知识求出圆心位置。。
以圆心和多边形两个相邻的顶点构成三角形,一共有n个这样的三角形。
求每个三角形与圆相交的面积,累加求和。
分三种情况:
case1: 两点都在圆内,相交面积即为三角形面积
case2: 一个点在圆内,一个在外部,相交面积为小三角形面积+扇形面积
case3:两点在圆外,考虑线段与圆交点数:
小于2时,相交面积为扇形面积;
等于2,相交面积为大扇形面积-小扇形面积+小三角形面积
利用叉积可以求出三角形面积
扇形面积公式为 angRR*0.5, ang为扇形所对应的圆心角
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
int dcmp(double x){if (fabs(x)<eps) return 0;return x<0?-1:1;}//精度比较
struct Point{ //点结构体
double x,y; //
Point (double x=0, double y=0):x(x),y(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); }
double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y;}
double Cross(Vector A,Vector B) {return A.x*B.y-A.y*B.x;}
double Length(Vector A) {return sqrt(Dot(A,A));}
double Angle(Vector A,Vector B) { return acos(Dot(A,B)/Length(A)/Length(B));}
bool OnSegment(Point p, Point a1, Point a2){
return dcmp(Cross(a1-p,a2-p))==0 && dcmp(Dot(a1-p,a2-p))<=0;
}
struct Line{ //直线结构体
Point p; //点
Vector v; //向量判方向即速度
Line(){} //空构造器
Line(Point p,Vector v):p(p),v(v){}//构造函数
Point point(double t){ //传入时间
return p+v*t; //从P点往V方向走T时间后的点
}
};
struct Circle{ //圆结构体
Point c; //点
double r; //半径
Circle(){} //空构造器
Circle(Point c, double r):c(c),r(r){}//圆
};
int getLineCircleIntersection(Line L,Circle C, vector<Point>& sol){
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; //无解,则无交点
double t1,t2; //声明两个解
if (dcmp(delta)==0) { //有一个解,一个交点
t1 = t2 = -f/(2*e);
sol.push_back(L.point(t1));
return 1;
}
t1 = (-f-sqrt(delta))/(2*e); sol.push_back(L.point(t1));
t2 = (-f+sqrt(delta))/(2*e); sol.push_back(L.point(t2));
return 2;//有两个解,两个交点
}
double get_Circle_polygon_Intersect_area(Point A, Point B,Circle C){//传入线段与圆
Vector CA = C.c-A, CB = C.c-B; //圆心与线段两点向量
double da = Length(CA), db = Length(CB);//圆心与两点的距离
da = dcmp(da-C.r); //圆心到两点的距离与当前圆的半径之差
db = dcmp(db-C.r); //圆心到两点的距离与当前圆的半径之差
if (da<=0 && db<=0)return fabs(Cross(CA,CB))*0.5;//如果半径大于等于到两点距,直接就是三角形面积
vector<Point>sol; //开一个动态数组存点
int num = getLineCircleIntersection(Line(A,B-A),C,sol);//求出线圆的交点并存到SOL里面
double cnt = C.r*C.r; //得到半径的平方
Point q;
if (da<=0 && db>0) {//如果A点在圆内,B点在圆外,相交面积为小三角形面积+扇形面积
q = OnSegment(sol[0],A,B)?sol[0]:sol[1];
double area = fabs(Cross(CA,C.c-q))*0.5;
double ang = Angle(CB,C.c-q);
return area+cnt*ang*0.5;
}
if (db<=0 && da>0) {//如果B点在圆内,A点在圆外,相交面积为小三角形面积+扇形面积
q = OnSegment(sol[0],A,B)?sol[0]:sol[1];
double area = fabs(Cross(CB,C.c-q))*0.5;
double ang = Angle(CA,C.c-q);
return area+cnt*ang*0.5;
}
if (num==2) {//第四种情况,就是两点都在圆外且有圆与线段有两个交点,相交面积为大扇形面积-小扇形面积+小三角形面积
double big_area = cnt*Angle(CA,CB)*0.5;
double small_area = cnt*Angle(C.c-sol[0],C.c-sol[1])*0.5;
double delta_area = fabs(Cross(C.c-sol[0],C.c-sol[1]))*0.5;
return big_area+delta_area-small_area;
}
return cnt*Angle(CA,CB)*0.5;//第五种情况就是两点都在圆外且有圆与线段的交点个数小于2,相交面积为扇形面积;
}
Circle bomb;
double X1,Y1,R,X0,Y0,h,x,y;
const int maxn = 100000+5;
Point p[maxn];
int n;
int main(){
while (scanf("%lf%lf%lf",&X0,&Y0,&h)==3){//输入三个数代表小白投弹的坐标(x,y,h)
scanf("%lf%lf",&X1,&Y1); //输入两个数代表飞机当前的速度(x1, y1);
scanf("%lf",&R); //输入炸弹的爆炸半径R;
double t = sqrt(0.2*h); //得到时间,因为二分之一GT方等于H,G取10
bomb = Circle(Point(X0+X1*t,Y0+Y1*t),R);//炸弹炸出的圆
scanf("%d",&n); //输入一个数n,代表岛屿由n个点组成;
for (int i=0; i<n; i++){ //遍历全部点
scanf("%lf%lf",&x,&y); //每行输入一个(x',y')坐标,代表岛屿的顶点
p[i] = Point(x,y); //把点打进数组里面
}
p[n] = p[0]; //第零个点也是第N个个点,因为要连边
double area = 0; //初始化答案是0
for (int i=0; i<n; i++) { //逐条边扫
double tmp = get_Circle_polygon_Intersect_area(p[i],p[i+1],bomb);//得相交区域面积
if (Cross(p[i]-bomb.c,p[i+1]-bomb.c)<0) tmp = -tmp;//利用差乘保持,圆心乘点心的向量大于零不变,小于零取反
area += tmp; //答案累加
}
if (area<0) area = -area; //因为是用叉乘算面积且顺逆方向不定故取正
printf("%.2f\n",area); //输出
}
return 0;
}
/*
Sample Input
0 0 2000 100 0 100 4 1900 100 2000 100 2000 -100 1900 -100
Sample Output
15707.96
*/