1. 题目重述
在平面上有一个直角三角形,三个顶点为
(
0
,
0
)
,
(
n
,
0
)
,
(
0
,
n
)
(0,0),(n,0),(0,n)
(0,0),(n,0),(0,n)。
一架飞机最初位于内部点
(
x
,
y
)
(x,y)
(x,y)并以速度
(
v
x
,
v
y
)
(v_x,v_y)
(vx,vy)做匀速运动。每当飞机到达三角形的任意一条边(但不是顶点)时,按照“入射角等于反射角”原则折返;若恰好到达某个顶点,则视为成功逃逸。求飞机是否能逃逸,若能,统计其在逃逸前共碰壁(反射)的次数;若永不逃逸,输出
−
1
-1
−1。
2. 输入输出格式
输入
第一行整数
t
t
t(测试组数)。
每组一行,包含五个整数
n
,
x
,
y
,
v
x
,
v
y
n,x,y,v_x,v_y
n,x,y,vx,vy
满足
3
≤
n
≤
1
0
9
3\le n\le10^9
3≤n≤109,
1
≤
x
,
y
1\le x,y
1≤x,y,且
x
+
y
<
n
x+y<n
x+y<n,
1
≤
v
x
,
v
y
≤
1
0
9
1\le v_x,v_y\le10^9
1≤vx,vy≤109。
输出
每组输出一个整数:若飞机能逃逸则为其在顶点前的反射次数,否则为
−
1
-1
−1。
3. 核心思路
“镜面反射”转化为“无界平面镜像铺瓦”:
-
镜像展开
把每次遇到一条边的反射都看作飞机不反射、继续直线飞行,但在那条边所在的直线的另一侧“镜像”一个三角形。这么做之后,原三角形的顶点在平面上会生成许多镜像顶点。 -
直线与镜像交点
飞机能逃逸当且仅当其直线轨迹恰好经过某个镜像顶点。我们只需求解存在一个非负时间 t t t,使
( x + v x t , y + v y t ) (x+v_x\,t,y+v_y\,t) (x+vxt,y+vyt)
恰好落在某个 ( k 1 n , k 2 n ) (k_1n,k_2n) (k1n,k2n)或斜边镜像对应的位置上。 -
同余方程+CRT
将对两条直角边的“越界”条件转化为同余:
v x t ≡ − x ( m o d n ) , v y t ≡ − y ( m o d n ) v_x\,t\equiv -x\pmod n,\quad v_y\,t\equiv -y\pmod n vxt≡−x(modn),vyt≡−y(modn)
用扩展欧几里得解出每个同余的最小周期解,再用中国剩余定理合并,得到最小非负解 t t t。若无解,输出 − 1 -1 −1。
4. 详细步骤
-
速度归一:令 g = gcd ( v x , v y ) g=\gcd(v_x,v_y) g=gcd(vx,vy),更新 ( v x , v y ) ← ( v x / g , v y / g ) (v_x,v_y)\leftarrow(v_x/g,v_y/g) (vx,vy)←(vx/g,vy/g)。
-
解同余
- 解方程
v
x
t
≡
−
x
(
m
o
d
n
)
v_x\,t\equiv -x\pmod n
vxt≡−x(modn),记解为
t ≡ r 1 ( m o d m 1 ) , m 1 = n gcd ( v x , n ) . t\equiv r_1\pmod{m_1},\quad m_1=\tfrac n{\gcd(v_x,n)}. t≡r1(modm1),m1=gcd(vx,n)n. - 解方程
v
y
t
≡
−
y
(
m
o
d
n
)
v_y\,t\equiv -y\pmod n
vyt≡−y(modn),记解为
t ≡ r 2 ( m o d m 2 ) , m 2 = n gcd ( v y , n ) . t\equiv r_2\pmod{m_2},\quad m_2=\tfrac n{\gcd(v_y,n)}. t≡r2(modm2),m2=gcd(vy,n)n.
若任一不可解,则直接输出 − 1 -1 −1。
- 解方程
v
x
t
≡
−
x
(
m
o
d
n
)
v_x\,t\equiv -x\pmod n
vxt≡−x(modn),记解为
-
CRT合并
用扩展欧几里得把上面两组同余合并成
t ≡ r ( m o d l c m ( m 1 , m 2 ) ) , t\equiv r\pmod{\mathrm{lcm}(m_1,m_2)}, t≡r(modlcm(m1,m2)),
取最小非负解 t t t。 -
计算逃逸点
X = x + v x t , Y = y + v y t . X=x+v_x\,t,\quad Y=y+v_y\,t. X=x+vxt,Y=y+vyt. -
统计反射次数
在“镜像平面”中,飞机从 ( x , y ) (x,y) (x,y)飞到 ( X , Y ) (X,Y) (X,Y):
- 竖边 x = 0 , n x=0,n x=0,n:穿越次数为 ⌊ X / n ⌋ − ⌊ ( x − 1 ) / n ⌋ \lfloor X/n\rfloor-\lfloor(x-1)/n\rfloor ⌊X/n⌋−⌊(x−1)/n⌋.
- 横边 y = 0 , n y=0,n y=0,n:穿越次数为 ⌊ Y / n ⌋ − ⌊ ( y − 1 ) / n ⌋ \lfloor Y/n\rfloor-\lfloor(y-1)/n\rfloor ⌊Y/n⌋−⌊(y−1)/n⌋.
- 斜边 x + y = n x+y=n x+y=n:令 u = x + y + n , U = X + Y + n u=x+y+n,\;U=X+Y+n u=x+y+n,U=X+Y+n,穿越次数 ⌊ U / ( 2 n ) ⌋ − ⌊ ( u − 1 ) / ( 2 n ) ⌋ \lfloor U/(2n)\rfloor-\lfloor(u-1)/(2n)\rfloor ⌊U/(2n)⌋−⌊(u−1)/(2n)⌋.
- 另一条镜像斜边 x − y = 0 x-y=0 x−y=0:令 v = x − y + n , V = X − Y + n v=x-y+n,\;V=X-Y+n v=x−y+n,V=X−Y+n,穿越次数 ⌊ V / ( 2 n ) ⌋ − ⌊ ( v − 1 ) / ( 2 n ) ⌋ \lfloor V/(2n)\rfloor-\lfloor(v-1)/(2n)\rfloor ⌊V/(2n)⌋−⌊(v−1)/(2n)⌋。
-
减去起点终点假穿越
前面统计中,若起点或终点恰在边界上,会被当做一次“穿越”计数,实际不算反射,需减去2;若终点恰在主斜边 x + y = n x+y=n x+y=n上,又会被重复统计,多减去2。 -
输出
综合上述统计即为答案。
#include <bits/stdc++.h>
#define int long long
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod = 1e9 + 7;
// 扩展欧几里得:求 ax + by = g 的一组解 (x, y),返回 gcd(a, b)
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int g = exgcd(b, a % b, y, x);
y -= a / b * x;
return g;
}
// 向下取整除:floor(a / b),兼顾 a 负数情形
int floor_div(int a, int b) {
if (a >= 0) return a / b;
// (a - b + 1) / b 等价于向下取整
return (a - b + 1) / b;
}
void solve() {
int n, x, y, v_x, v_y;
cin >> n >> x >> y >> v_x >> v_y;
// 1. 归一化速度向量,减少同余计算系数大小
int g0 = gcd(v_x, v_y);
v_x /= g0;
v_y /= g0;
// 2. 构造同余方程:vx * t ≡ -x (mod n)
int mod1, rem1;
{
int u, v;
int g = exgcd(v_x, n, u, v);
if (x % g != 0) {
cout << -1 << '\n';
return;
}
mod1 = n / g;
// 特解 t0 = (-x/g) * u
rem1 = (-x / g) * u % mod1;
if (rem1 < 0) rem1 += mod1;
}
// 3. 构造同余方程:vy * t ≡ -y (mod n)
int mod2, rem2;
{
int u, v;
int g = exgcd(v_y, n, u, v);
if (y % g != 0) {
cout << -1 << '\n';
return;
}
mod2 = n / g;
rem2 = (-y / g) * u % mod2;
if (rem2 < 0) rem2 += mod2;
}
// 4. CRT 合并 t ≡ rem1 (mod mod1) 与 t ≡ rem2 (mod mod2)
int u, v;
int g = exgcd(mod1, mod2, u, v);
if ((rem2 - rem1) % g != 0) {
cout << -1 << '\n';
return;
}
int lcm = mod1 / g * mod2;
// 计算最小非负 t
int t = (rem2 - rem1) / g * u % (mod2 / g) * mod1 + rem1;
t = (t % lcm + lcm) % lcm;
// 5. 计算逃逸时刻对应的终点坐标 (X, Y)
int X = x + v_x * t;
int Y = y + v_y * t;
// 6. 在镜像平面中统计穿过边界次数 —— 初始值 0
int ans = 0;
// 6.1 统计竖边 x=0,n 的穿越次数:每穿越一个整 n 单位,计 1 次
ans += X / n - (x - 1) / n;
// 6.2 统计横边 y=0,n 的穿越次数:
// 理论上应当统计飞机从 y 到 Y 直线段与 y=k*n 的交点数。
// 原始错误在此处,将 Y 误写为 y,导致永远计算起点 y 而非终点位置。
// 正确做法:
ans += Y / n - (y - 1) / n;
// 解释:Y/n 表示终点位于第几个水平带,(y-1)/n 表示起点在第几个水平带,再相减即为穿越次数。
// 6.3 统计斜边 x+y=n 的穿越次数:
{
// u0 = x+y+n 映射到 2n 周期,u1 = X+Y+n 同理
int u0 = x + y + n;
int u1 = X + Y + n;
if (u0 > u1) swap(u0, u1);
ans += u1 / (2 * n) - (u0 - 1) / (2 * n);
}
// 6.4 统计另一斜边 x-y=0 的穿越次数:
{
int v0 = x - y + n;
int v1 = X - Y + n;
if (v0 > v1) swap(v0, v1);
ans += floor_div(v1, 2 * n) - floor_div(v0 - 1, 2 * n);
}
// 7. 修正:去掉起点与终点在边界上的“假”穿越计数
// 前面统计时,(x,y) 和 (X,Y) 落在边界会被算作一次穿越,但实际反射不算
ans -= 2;
// 如果终点恰好落在斜边 x+y=n,也会在 x+y 家族和 x-y 家族中重复统计,多减 2
if ((X + Y) % (2 * n) == n) ans -= 2;
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
cin >> T;
while (T--) solve();
return 0;
}