题解:CF2097C Bermuda Triangle
chenzhaoxu2027 · · 题解
简述题意
- 有一个等腰直角三角形的台球桌,三个顶点分别是
(0,0),(0,n),(n,0) ,一个球位于坐标为(x,y) 的位置,现在沿方向向量为(v_x,v_y) 的方向将这个球击打出去,每一秒运动\sqrt{v_x^2+v_y^2} 的长度。这个球桌没有摩擦力,但是碰到边就会反弹,球到达这个三角形的顶点时就会进袋,求这个球碰撞几次之后才会进袋,或者输出无解。
分析
这个问题说球可以反弹,十分的难以计算。我们不妨考虑球匀速直线运动,但是球桌通过轴对称布满平面。我们惊人的发现去找球的运动轨迹与密铺三角形的顶点的交点的效果和原问题是等价的。
首先考虑判断无解。这一步我们将时间的秒数化为整数方便计算,那么我们只需要将
然后我们假设进袋时间是
分别解这两个方程可以使用 exgcd。如果这两个方程的任何一个无解,那么这个问题就无解。
但是这两个方程解出来的
根据二元一次方程通解公式,我们可以得到:
其中
好,有了
我们分 4 类考虑:竖直的、水平的、左下往右上斜的和左上往右下斜的。前两类只需要使用终点的对应坐标除以
左上往右下斜的线上的点的坐标满足
另一种斜向的线同理。需要注意的是,另一种斜向的线上的点满足差值是定值,如果直接套用上面的式子会错,得加一个绝对值。
最后的问题:处理终点的额外计算的线的个数。这里我想到了一种偷懒的办法,还是以左上往右下斜的线为例,如果终点位于这根线上,那么一定有:
然后就做完了,值得注意的是这种方式的中间步骤可能爆 long long,得用 int128。听着很复杂实际上写着比较简单。
代码
#include<bits/stdc++.h>
using namespace std;
#define int __int128
int gcd(int a,int b){
if(b==0){
return a;
}
return gcd(b,a%b);
}
int lcm(int a,int b){
return a*b/gcd(a,b);
}
void exgcd(int a,int b,int &x,int &y){
if(b==0){
x=1;
y=0;
return;
}
exgcd(b,a%b,x,y);
int x_=x,y_=y;
x=y_;
y=x_-(a/b)*y_;
}
int read(){}//快读快写省略,目的是配套int128
void write(int x){}
signed main(){
int t=read();
while(t--){
int n=read(),x=read(),y=read(),vx=read(),vy=read();
int g=gcd(vx,vy);
vx/=g;
vy/=g;
if(x%gcd(vx,n)!=0||y%gcd(vy,n)!=0){
cout<<-1<<"\n";
continue;
}
int gx=gcd(vx,n),gy=gcd(vy,n);
int mx,kx,my,ky;
exgcd(vx/gx,n/gx,mx,kx);
exgcd(vy/gy,n/gy,my,ky);
mx*=x/gx;
my*=y/gy;
mx=-mx;
my=-my;//注意正负性啊
if(max(my-mx,mx-my)%gcd(n/gx,n/gy)!=0){
cout<<-1<<"\n";
continue;
}
int G=gcd(n/gx,n/gy);
int k,l;
exgcd(n/gx/G,n/gy/G,k,l);
l=-l;
k*=(my-mx)/G;
int M=mx+n/gx*k;
M=(M%n+n)%n;//M可能不在范围内,由于保证了vx与vy互质,那么周期一定是n
int X=M*vx+x,Y=M*vy+y;
int bx=X/n-1,by=Y/n-1,bs=(X+Y)/(2*n),bt=(max(Y-X,X-Y))/(2*n);//CF上面int128不让用abs,只能这么写qwq
write(bx+by+bs+bt);
cout<<"\n";
}
return 0;
}