2020 Multi-University Training Contest 3 H / HDU 6798 - Triangle Collision (平面幾何、二分)
2020 Multi-University Training Contest 3 - H. Triangle Collision
題意
有一小球在一個邊長為\(L\)的等邊三角形內運動
其擁有一個初始位置\((x,y)\)以及恆定速度\((V_x,V_y)\)
詢問當第\(k\)次撞擊三角形邊緣時花費的時間
保證小球在前\(k\)次撞擊不會撞到三角形的某個角
思路
先放一篇學長的部落格《Triangle Collision(二分 平面幾何)》 JK Chen寫得很清晰,我也是看完後才換了想法過的這題
實際上如果我們可以不把小球的碰撞當作一種平面反射,而是把整個平面視作是由無限個等邊三角形組合而成的
於是我們就能夠把平面看作是由無數條\(y=b,\ y=\sqrt 3x+b,\ y=-\sqrt 3x+b\)這三類直線組成的
每類直線兩兩之間距離\(\frac {\sqrt 3}{2}L\),且三類直線各有一條線交於原點
假設我們知道了終點,那麼碰撞次數也就等同於起點到終點這條線段與圖中三類直線的交點個數
於是我們可以想到,嘗試二分距離或者是時間,計算出終點後判斷即可
那麼問題就變成了線段與平面內直線的交點個數
既然已經將直線分成三類了,那就可以一類一類地去求交點個數
考慮根據三類直線重新選取座標系
如果選擇形如\(y=b\)直線的座標系,略去其餘兩類直線後,平面內將只剩下下圖所示
此時,終點與起點的\(y\)
如果線段往\(y-\)方向移動
此時,\(\frac {\Delta y-y_0}{h}+1\)是這條線段與直線\(y=b\)類的交點個數
如果轉換了座標系,那麼我們可以拿初始的三角形的邊作為底邊(新座標系的\(x\)軸),用相同的處理方式求出交點個數
三種情況的交點個數和即為該條線段的交點個數
完整程式
各種座標系對應的底邊最好畫圖觀察下
旋轉後的向量對應的座標系也最好畫圖看下
(感覺關鍵就在於旋轉)
(343ms/2000ms)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
bool operator == (Point b)const{
return dbcmp(x-b.x)==0&&dbcmp(y-b.y)==0;
}
Point operator -(const Point &b)const{ //叉積
return Point(x-b.x,y-b.y);
}
double operator ^(const Point &b)const{ //點積
return x*b.y-y*b.x;
}
double operator *(const Point &b)const{
return x*b.x+y*b.y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
double distance(Point p){
return hypot(x-p.x,y-p.y);
}
Point Rotate(double rad){ //逆時針旋轉
return Point(x*cos(rad)-y*sin(rad),x*sin(rad)+y*cos(rad));
}
};
struct Line{
Point s,e;
Line(){}
Line(Point _s,Point _e){
s=_s;
e=_e;
}
bool pointonseg(Point p){
return dbcmp((p-s)^(e-s))==0&&dbcmp((p-s)*(p-e))<=0;
}
double getDistance(Point A){ //點到直線的距離
return fabs((A-s)^(A-e)/s.distance(e));
}
Point crosspoint(Line v){
double a1=(v.e-v.s)^(s-v.s);
double a2=(v.e-v.s)^(e-v.s);
return Point((s.x*a2-e.x*a1)/(a2-a1),(s.y*a2-e.y*a1)/(a2-a1));
}
};
const double sq3=sqrt(3);
double L,x,y,vx,vy,h;
int k;
Point A,B,C,oripot,v1,v2,v3;
Line AB,AC,BC;
bool check(double tim)
{
ll kk=0;
double dis;
dis=BC.getDistance(oripot)+v1.y*tim; //第一種座標系對應邊BC,計算出起點高度+y軸經過距離Δy
if(dis<0)
kk+=(ll)((-dis)/h)+1; //如果方向為負方向,計算結果與直接整除略有不同
else
kk+=(ll)(dis/h); //正方向正常整除
dis=AC.getDistance(oripot)+v2.y*tim; //第一種座標系對應邊AC,計算出起點高度+y軸經過距離Δy
if(dis<0)
kk+=(ll)((-dis)/h)+1;
else
kk+=(ll)(dis/h);
dis=AB.getDistance(oripot)+v3.y*tim; //第一種座標系對應邊AB,計算出起點高度+y軸經過距離Δy
if(dis<0)
kk+=(ll)((-dis)/h)+1;
else
kk+=(ll)(dis/h);
return kk>=k; //最後判斷當前的交點個數是否大於等於要求的個數
}
void solve()
{
cin>>L>>x>>y>>vx>>vy>>k;
h=sq3*L/2.0; //三角形高度
v1=Point(vx,vy); //第一種座標系下的速度向量
v2=v1.Rotate(PI*2.0/3); //第二種座標系下的速度向量(逆時針旋轉120°)
v3=v1.Rotate(-PI*2.0/3); //第三種座標系下的速度向量(順時針旋轉120°)
oripot=Point(x,y); //小球初始位置
A=Point(0,h); //三角形上角
B=Point(L/2.0,0); //三角形右角
C=Point(-L/2.0,0); //三角形左角
AB=Line(A,B); //三條邊組成的線
AC=Line(A,C);
BC=Line(B,C);
double l=0,r=1e10,mid; //二分時間,計算交點個數是否符合條件
while(r-l>=1e-5) //題目不卡精度,實測1e-4也能過
{
mid=(l+r)/2.0;
if(check(mid))
r=mid;
else
l=mid;
}
cout<<r<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cout<<fixed<<setprecision(8);
int T;cin>>T;
for(int t=1;t<=T;t++)
solve();
return 0;
}