题解 P4682 【[ZJOI2007]粒子运动】
liaohaoping · · 题解
这题数据范围并不大,显然不用什么复杂的优化,求出每个粒子每段(这里简称某粒子两次碰撞之间的部分为一段,包括时刻0到第一次碰撞)的信息就行了,通过观察可发现,只需要处理好以下三个问题(为了方便,我们把所有坐标分别减去圆心的坐标,再把圆心的坐标改为(0,0)):
1、某粒子在某段与圆下次碰撞的位置
2、某粒子在某次碰撞后vx,vy的变化
3、粒子a的某一段与粒子b的某一段(保证某一时刻这两段同时存在)的历史最近距离
先看问题1,若此段起始点为(stx,sty),速度为(vx,vy),设碰撞时间为t,则(stx+vx·t)²+(sty+vy·t)²=r² → stx²+vx²·t²+2·stx·vx·t+sty²+vy²·t²+2·sty·vy·t-r²=0 → (vx²+vy²)·t²+(2·stx·vx+2·sty·vy)·t+(stx²+sty²-r²)=0,我们设a=vx²+vy²,b=2·stx·vx+2·sty·vy,c=stx²+sty²-r²,则t=-b+sqrt(b²-4ac)/2a(舍去小解,小解一定不成立),再通过t算出位置即可。
问题2也不难想,若与圆的碰撞点为(x,y),原速为(x1,x2),反弹后为(x2,y2),不难得出atan2(x2,y2)-atan2(x,y)=atan2(x,y)-atan2(x1,y1)(角相等),即atan2(x2,y2)=2·atan2(x,y)-atan2(x1,y1),设k=x2/y2,则k=tan(2·atan2(x,y)-atan2(x1,y1)),因为速度相同,所以x1²+y1²=x2²+y2²,设z=x1²+y1²,则y2²+k²·y2²=z → (k²+1)·y2²=z → y2=sqrt(x1²+y1²/k²+1),然后通过y2求出x2,需要注意的是,(-x2,-y2)在这里同样成立,我们可以通过带入一个点判断是否在圆内来判断取哪个解。
问题3稍微难一点,为了方便,我们设tt=max(t1,t2)(t1,t2分别为两段开始的时刻),若(x1,y1),(x2,y2)分别为tt时刻两粒子的坐标,(vx1,vy1),(vx2,vy2)分别为两段的速度,y为两粒子间距离的平方,y=((x1+vx1·t)-(x2+vx2·t))²+((y1+vy1·t)-(y2+vy2·t))² → y=((x1-x2)+(vx1-vx2)·t)²+((y1-y2)+(vy1-vy2)·t)²我们设xx=x1-x2,yy=y1-y2,vx=vx1-vx2,vy=vy1-vy2,那么y=(xx+vx·t)²+(yy+vy·t)² → y=xx²+vx²·t²+2·xx·vx·t+yy²+vy²·t²+2·yy·vy·t → y=(vx²+vy²)·t²+(2·xx·vx+2·yy·vy)·t+(xx²+yy²),再设a=vx²+vy²,b=2·xx·vx+2·yy·vy,c=xx²+yy²,然后求出最小值y3=4ac-b²/4a即为历史最近距离,特殊情况特判一下就行了。
我们一开始先用1、2点处理出每个粒子的每一段,再两个两个粒子求历史最近距离,找最小值即为答案。
代码
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read()
{
int x=0,w=0;char ch=getchar();
while(ch<'0'||ch>'9')w|=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
struct line{double stx,sty,vx,vy,edt;};
struct particle{line f[110];}p[110];
int n,k;double r,z=0.000000001;
inline void get_p(int i,int j,double stx,double sty,double vx,double vy)
{
p[i].f[j].stx=stx,p[i].f[j].sty=sty,p[i].f[j].vx=vx,p[i].f[j].vy=vy;
double a=vx*vx+vy*vy,b=2*stx*vx+2*sty*vy,c=stx*stx+sty*sty-r*r;
double t=(-b+sqrt(b*b-4*a*c))/(2*a);
p[i].f[j].edt=p[i].f[j-1].edt+t;//求碰撞点
if(j<k)
{
double edx=stx+vx*t,edy=sty+vy*t;
double kk=tan(atan2(edx,edy)*2-atan2(vx,vy));
double vy2=sqrt((vx*vx+vy*vy)/(kk*kk+1)),vx2=kk*vy2;
double tt=sqrt(r)/100000/((vx2+vy2)/2);tt=tt<0?-tt:tt;
double xx=(edx+vx2*tt),yy=(edy+vy2*tt);
if(xx*xx+yy*yy>=r*r)vx2=-vx2,vy2=-vy2;
get_p(i,j+1,edx,edy,vx2,vy2);//反弹
}
}
inline double dis(double x1,double y1,double x2,double y2)
{
if(x1==x2&&y1==y2)return 0;
double x=x1-x2,y=y1-y2;
return sqrt(x*x+y*y);
}
int main()
{
register double xx,yy;register int i,j,ii,jj;
scanf("%lf%lf%lf",&xx,&yy,&r);n=read(),k=read();
for(i=1;i<=n;++i)
{
double x,y,vx,vy;scanf("%lf%lf%lf%lf",&x,&y,&vx,&vy);
x-=xx;y-=yy;p[i].f[0].edt=0;get_p(i,1,x,y,vx,vy);
}
double ans=1e50;
for(i=2;i<=n;++i)
{
for(j=1;j<i;++j)
{
jj=1;
for(ii=1;ii<=k;++ii)//第一个粒子处于第ii段
{
--jj;
while(p[i].f[ii].edt>p[j].f[jj].edt&&jj<k)//判断为了使有某时刻两段(ii,jj+1)同时存在,ii是否需要++
{
++jj;
double t1=p[i].f[ii-1].edt,t2=p[j].f[jj-1].edt;
double x1=p[i].f[ii].stx,x2=p[j].f[jj].stx,y1=p[i].f[ii].sty,y2=p[j].f[jj].sty;
double vx1=p[i].f[ii].vx,vx2=p[j].f[jj].vx,vy1=p[i].f[ii].vy,vy2=p[j].f[jj].vy;
if(vx1==vx2&&vy1==vy2)//如果速度,方向相等,进行特殊处理
{
double t=max(t1,t2),xx1=x1+vx1*(t-t1),xx2=x2+vx2*(t-t2),yy1=y1+vy1*(t-t1),yy2=y2+vy2*(t-t2);
ans=min(ans,dis(xx1,yy1,xx2,yy2));
}
else
{
double xx1=x1+vx1*(t1<t2?t2-t1:0),xx2=x2+vx2*(t1>t2?t1-t2:0),yy1=y1+vy1*(t1<t2?t2-t1:0),yy2=y2+vy2*(t1>t2?t1-t2:0);
double vx=vx1-vx2,vy=vy1-vy2;xx=xx1-xx2,yy=yy1-yy2;
double a=vx*vx+vy*vy,b=2*xx*vx+2*yy*vy,c=xx*xx+yy*yy;
double t=-(b/(2*a));
if(t<0)ans=min(ans,dis(xx1,yy1,xx2,yy2));//如果t<0,取最近的合法值(t=0)
else if(t+max(t1,t2)>=p[i].f[ii].edt+z||t+max(t1,t2)>=p[j].f[jj].edt+z)//如果该时刻超出任意一段范围,取最近的合法值
{
t=min(p[i].f[ii].edt,p[j].f[jj].edt),xx1=x1+vx1*(t-t1),xx2=x2+vx2*(t-t2),yy1=y1+vy1*(t-t1),yy2=y2+vy2*(t-t2);
ans=min(ans,dis(xx1,yy1,xx2,yy2));
}
else//否则正常计算
{
double d=sqrt((4*a*c-b*b)/(4*a));
ans=min(ans,d);
}
}//求距离
}
}
}
}
printf("%.3lf",ans);
return 0;
}