CodeForces - 982E (扩展欧几里得算法、数论)

题目链接

题意:给你一个n*m的矩阵,从矩阵中的一点(x,y)以(vx,vy)的方向出发,碰到矩阵壁反弹, 若能从矩阵的四个角出去,则输出出去点的坐标,若不能则输出-1。

这题刚开始想用模拟,看其是否能出去,结果超时了啊T^T,超时就很难受了,然后去看了一下大佬的代码,。。是真的长。。本来想放弃的,然后想着都已经开始了,还是看看把,真的佩服这些大佬啊,。。对扩展欧几里得不够熟练也是出现了很多问题,一直wa是真的难受。去看了一下扩展欧几里得算法,还是没有很懂,还是要多练习啊。

题解:大佬题解

思路:这题我们考虑从(x,y)出发,经过矩阵壁反弹,但是我们可以看做把矩阵翻折出去,就可以把其路径看做一条直线,这样就能把它的路径简单化。然后我们延长路径到(0,y0)上,我们可以看做从(0,y0)出发,我们可以看做一个斜率为1的直线,那么y0 = y-x,所以我们可以得到公式 
a*n + (y-x) = b*m。然后我们转换一下公式a*n + b*m = (x-y) 那么我们就可以用扩展欧几里得来求出a*n + b*m = gcd(n, m)。如果(x-y)%gcd(n,m) == 0,说明这题有解,反之无解输出-1。然后我们要取a的尽量小的正整数值,不能只对m取模,要对m/gcd(n,m)取模,然后根据算出来的a和b的奇偶性来确定到达的位置。
           然后根据一开始输入的方向不同,我们把它都先转换为(1,1)的方向,然后最后再转换回去就好了。如果vx = -1 ,我们设置一个变量fx = 1,后面判断是否要转换回来,并且使x = n-x,y同理。大概就是这么个思路,但是其实自己没有很懂呀。

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
int n, m, x, y, vx, vy;

ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!b){
        x = 1;
        y = 0;
        return a;
    }
    int ans = exgcd(b, a%b, y, x);
    y -= (a/b)*x;
    return ans;
}

int main(){
    scanf("%d%d%d%d%d%d",&n,&m,&x,&y,&vx,&vy);
    if(x%n==0&&y%m==0){          //开始就在四角的点上
        printf("%d %d\n",x,y);
        return 0;
    }
    if(vx == 0){             //预处理判断vx等于0的情况
        if(x%n == 0){
            if(vy == 1)
                printf("%d %d\n",x, m);
            else
                printf("%d %d\n",x, 0);
        }
        else
            puts("-1");
        return 0;
    }
    if(vy == 0){        //预处理判断vy等于0的情况
        if(y%m == 0){
            if(vx == 1)
                printf("%d %d\n",n, y);
            else
                printf("%d %d\n",0, y);
        }
        else
            puts("-1");
        return 0;
    }
    int fx = 0,fy = 0;
    if(vx == -1){      //x的方向为反的,则记录一下,并转换位置。(转换位置的原题,画一下图,应该就能看出来了)
        x = n - x;
        fx = 1;
    }
    if(vy == -1){
        y = m - y;
        fy = 1;
    }
    ll a, b;
    ll gcd = exgcd(n, m, a, b);     //用扩展欧几里得求出gcd,a,b
    if((x - y)%gcd!=0){
        puts("-1");
        return 0;
    }
    ll t = (x - y)/gcd;        //这里a*n + b*m = gcd(n,m) => a'*n + b'*m = (x-y)
    a *= t;
    b *= t;
    ll m1 = m/gcd,n1 = n/gcd;      //我们要使a和b尽量小,则把除数变到最小
    ll a1 = (a%m1+m1+m1-1)%m1+1;    //加两个m1防止出现负数,-1:为了使a1不为0,后面再加一就好了
    ll b1 = -((x - y)-a1*n)/m;     //根据a*n + b*m = (x-y) 转换成
    ll ansx, ansy;
    if(a1&1)     //奇数的时候,x在n的位置
        ansx = n; 
    else
        ansx = 0;
    if(b1&1)
        ansy = m;
    else
        ansy = 0;
    if(fx)    //判断之前是否转换过。
        ansx = n-ansx; 
    if(fy)
        ansy = m-ansy;
    printf("%lld %lld\n",ansx, ansy);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhuyou_/article/details/81216193