Codeforces Round 1021 (Div. 1)-C. Bermuda Triangle

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 3n109 1 ≤ x , y 1\le x,y 1x,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 1vx,vy109

输出
每组输出一个整数:若飞机能逃逸则为其在顶点前的反射次数,否则为 − 1 -1 1


3. 核心思路

“镜面反射”转化为“无界平面镜像铺瓦”:

  1. 镜像展开
    把每次遇到一条边的反射都看作飞机不反射、继续直线飞行,但在那条边所在的直线的另一侧“镜像”一个三角形。这么做之后,原三角形的顶点在平面上会生成许多镜像顶点。

  2. 直线与镜像交点
    飞机能逃逸当且仅当其直线轨迹恰好经过某个镜像顶点。我们只需求解存在一个非负时间 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)或斜边镜像对应的位置上。

  3. 同余方程+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 vxtx(modn),vyty(modn)
    用扩展欧几里得解出每个同余的最小周期解,再用中国剩余定理合并,得到最小非负解 t t t。若无解,输出 − 1 -1 1


4. 详细步骤

  1. 速度归一:令 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)

  2. 解同余

    • 解方程 v x   t ≡ − x ( m o d n ) v_x\,t\equiv -x\pmod n vxtx(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)}. tr1(modm1),m1=gcd(vx,n)n.
    • 解方程 v y   t ≡ − y ( m o d n ) v_y\,t\equiv -y\pmod n vyty(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)}. tr2(modm2),m2=gcd(vy,n)n.
      若任一不可解,则直接输出 − 1 -1 1
  3. CRT合并
    用扩展欧几里得把上面两组同余合并成
    t ≡ r ( m o d l c m ( m 1 , m 2 ) ) , t\equiv r\pmod{\mathrm{lcm}(m_1,m_2)}, tr(modlcm(m1,m2)),
    取最小非负解 t t t

  4. 计算逃逸点
    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.

  5. 统计反射次数
    在“镜像平面”中,飞机从 ( 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⌊(x1)/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⌊(y1)/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)⌋⌊(u1)/(2n)⌋.
  • 另一条镜像斜边 x − y = 0 x-y=0 xy=0:令 v = x − y + n ,    V = X − Y + n v=x-y+n,\;V=X-Y+n v=xy+n,V=XY+n,穿越次数 ⌊ V / ( 2 n ) ⌋ − ⌊ ( v − 1 ) / ( 2 n ) ⌋ \lfloor V/(2n)\rfloor-\lfloor(v-1)/(2n)\rfloor V/(2n)⌋⌊(v1)/(2n)⌋
  1. 减去起点终点假穿越
    前面统计中,若起点或终点恰在边界上,会被当做一次“穿越”计数,实际不算反射,需减去2;若终点恰在主斜边 x + y = n x+y=n x+y=n上,又会被重复统计,多减去2。

  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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值