题意:
给定n 个圆的圆心坐标,半径,以及运动速度的方向矢量,每两个圆相交时会融合成一个,形成的新圆与面积大小
所占的比例成正比,问最后一次发生融合的时间是多久,并且此时还剩下多少个圆。
题目链接:The Agglomerator
解题思路:
判断两圆是否相交很容易想到圆心距 和 半径和 之间的大小关系,设两圆的圆心坐标分别是 O1(x1, y1),O2(x2,y2)
,半径大小分别为 r1,r2, 速度矢量分别为 v1(v1x, v2x), v2(v2x, v2y),则t时间后两圆圆心分别处在 O1(x1 + t*v1x, y1 +
t*v1y), O2类似,然后两圆圆心距离 就可以算出,和r1+r2比较,化简后,就是
((v1 - v2)^ 2) * (t ^2) + 2*t*Dot(O1 - O2, v1 - v2) + ( (O1 -O2) ^ 2 ) - (r1 + r2) ^ 2 < 0
对应加减乘除 都是 向量 或者 点 的运算符,解这个方程就行,Dot 为点积
判断 delta = b*b - 4 * a * c 的正负,有解的话也要判断解是否非负,毕竟算的是时间,两解的情况,我们要取最小
的解mx,因为一相交就 融合了,然后更新所有点运动mx时间后的状态,合并这两个圆(删除两个,增加一个),这
里我用vector 删除时,没注意到选出来的o1,o2大小关系,结果浪费了不少时间,如果用vector的erase先删除小的o1
话,那么另外一个圆的编号o2所处的位置就变化了,所以应该先删除大的o2,另外注意时间t 不超过1e9,我初始的mx
小了,结果WA了好久。。。。。。
总之,几何还是需要耐心,注意细节。
代码:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdlib>
#include <set>
#include <queue>
#include <vector>
#include <map>
using namespace std;
const double eps = 0.0000001;
const double PI = acos(-1.0);
struct Point
{
double x, y;
Point(double xx, double yy) : x(xx), y(yy) {}
Point() {}
};
typedef Point Vector;
Vector operator - (Point A, Point B)
{
return Vector(A.x - B.x, A.y - B.y);
}
Vector operator + (Point A, Point B)
{
return Vector(A.x + B.x, A.y + B.y);
}
Vector operator * (Point A, double p)
{
return Vector(A.x * p, A.y * p);
}
Vector operator / (Point 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 pf(double t)
{
return t*t;
}
double Length(Vector A)
{
return sqrt( Dot(A, A));
}
//****分割线***/
struct Circle //圆的参数
{
Point c;
Vector v;
double r;
Circle(Point c, Vector v, double r) : c(c), v(v), r(r) {}
Circle() {}
};
vector<Circle> vec;
double delta(double a, double b, double c)
{
double t = b*b - 4*a*c;
return t;
}
int CircleIntersection(Circle A, Circle B, vector<double>& ch)//求两圆相交时的用的时间
{
double a = pf(Length(A.v - B.v) ); //对应二次方程的a,b,c系数
double b = 2 * Dot((A.c - B.c), (A.v - B.v) );
double c = pf(Length(A.c - B.c)) - pf(A.r + B.r);
double t =delta(a, b, c); //判别式
if(t < -eps) return 0; //无解
double cht = (sqrt(t) - b)/(2*a); //二次方程求根公式
if(cht > eps ) ch.push_back(cht);
cht = (-sqrt(t) - b)/(2*a);
if(cht > eps) ch.push_back(cht);
return ch.size();
}
Circle CombineCirlce(Circle A, Circle B) //合并圆A,B
{
double Area1 = PI * A.r * A.r;
double Area2 = PI * B.r * B.r;
double Area = Area1 + Area2;
Area1 /= Area, Area2 /= Area; //求出对应的比例
Circle o;
o.c = A.c * Area1 + B.c * Area2; //按比例算出新圆的参数
o.r = sqrt(pf(A.r) + pf(B.r) );
o.v = A.v * Area1 + B.v * Area2;
return o;
}
void update(double t) //经过t时间后,所有圆的状态更新
{
for(int i = 0,k = vec.size();i < k;i++) {
vec[i].c = vec[i].c + vec[i].v * t;
}
}
int main()
{
int n, flag;
double x, y, vx, vy, r, ans;
while(~scanf("%d",&n)) {
vec.clear();
for(int i = 0;i < n;i++) {
scanf("%lf %lf %lf %lf %lf",&x ,&y ,&vx, &vy, &r );
Circle ct = Circle(Point(x, y), Vector(vx, vy), r);
vec.push_back(ct);
}
flag = 0;
ans = 0;
while(vec.size() > 1) {
flag = 0;
double mx = 999999999999; //这个要设置大一些(原来double可以调这么大)
int o1, o2, cnt = vec.size();
for(int i = 0;i < cnt;i++) { //求出最先先交的两个圆
for(int j = i+1;j < cnt;j++) {
vector<double> ch;
if( CircleIntersection(vec[i], vec[j], ch)) {
if(ch.size() > 1 && ch[0] > ch[1]) swap(ch[0], ch[1]);
if(mx > ch[0]) {
flag = 1;
mx = ch[0];
o1 = i, o2 = j;
}
}
}
}
if(!flag) break; //剩下的圆都不相交了
update(mx); //更新
vec.push_back( CombineCirlce(vec[o1], vec[o2]) ); //新增圆
vec.erase(vec.begin() + o2); //删除
vec.erase(vec.begin() + o1);
ans += mx;
}
printf("%d %lf\n",vec.size(), ans);
}
return 0;
}