codeforce C. Ray Tracing(扩展欧几里得|模拟)

C. Ray Tracing
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

There are k sensors located in the rectangular room of size n × m meters. The i-th sensor is located at point (xi, yi). All sensors are located at distinct points strictly inside the rectangle.

Opposite corners of the room are located at points (0, 0) and (n, m). Walls of the room are parallel to coordinate axes.

At the moment 0, from the point (0, 0) the laser ray is released in the direction of point (1, 1). The ray travels with a speed of meters per second. Thus, the ray will reach the point (1, 1) in exactly one second after the start.

When the ray meets the wall it's reflected by the rule that the angle of incidence is equal to the angle of reflection. If the ray reaches any of the four corners, it immediately stops.

For each sensor you have to determine the first moment of time when the ray will pass through the point where this sensor is located. If the ray will never pass through this point, print  - 1 for such sensors.

Input

The first line of the input contains three integers nm and k (2 ≤ n, m ≤ 100 0001 ≤ k ≤ 100 000) — lengths of the room's walls and the number of sensors.

Each of the following k lines contains two integers xi and yi (1 ≤ xi ≤ n - 11 ≤ yi ≤ m - 1) — coordinates of the sensors. It's guaranteed that no two sensors are located at the same point.

Output

Print k integers. The i-th of them should be equal to the number of seconds when the ray first passes through the point where the i-th sensor is located, or  - 1 if this will never happen.

Examples
input
3 3 4
1 1
1 2
2 1
2 2
output
1
-1
-1
2
input
3 4 6
1 1
2 1
1 2
2 2
1 3
2 3
output
1
-1
-1
2
5
-1
input
7 4 5
1 3
2 2
5 1
5 3
4 3
output
13
2
9
5
-1
Note

In the first sample, the ray will consequently pass through the points (0, 0)(1, 1)(2, 2)(3, 3). Thus, it will stop at the point (3, 3) after3 seconds.

In the second sample, the ray will consequently pass through the following points: (0, 0)(1, 1)(2, 2)(3, 3)(2, 4)(1, 3)(0, 2),(1, 1)(2, 0)(3, 1)(2, 2)(1, 3)(0, 4). The ray will stop at the point (0, 4) after 12 seconds. It will reflect at the points (3, 3)(2, 4),(0, 2)(2, 0) and (3, 1).


题目大意:

一条射线从(0,0)射出,速度为√2,方向与x轴夹角45°,碰到边界会镜面反射,方向对称,速度不变,直到射到顶点被吸收。题目给的是一个n*m的空间,四周都是墙壁,然后有k个询问,每次询问的是空间内非角的一个点,你要得出的是最早到达这个点的时刻,如果不能到达则输出-1.


分析:

模拟射线的轨迹, 记录所有的起点,所谓的起点就是碰到墙布上的点,和起始点(0,0)。然后模拟4个运动方向,1斜线右上方,2为斜向左下,3为斜向左上,4为斜向右下。如果一个起点往1或者2方向运动,那么它的x-y的差始终是不会变的,因为x与y是同加同减的。同理往3或者4方向运动时候,该射线的x+y的和是不会变的,因为x与y的加减是异号的。

详细解释请看下面代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
const long long INF = 1e10 + 5;
long long vis1[N*2], vis2[N*2], vis3[N*2], vis4[N*2];
int n, m, k;
long long slove(int x, int y)
{
    long long res = INF, tmp;
    if(vis1[x - y + N] != -1)   //如果该点可以由1方向射过来
    {
        tmp = vis1[x - y + N];      //到达1方向的起点的时间
        if(x > y)           //因为1方向只能从最左边的墙和最下边的墙的起点射出来,所以看离最下边的墙近还是离最左边的墙近,那个近就是从哪射出来的
            tmp += y;
        else
            tmp += x;
        res = min(res, tmp);
    }
    if(vis2[x - y + N] != -1)
    {
        tmp = vis2[x - y + N];
        if(n - x > m - y)
            tmp += m - y;
        else
            tmp += n - x;
        res = min(res, tmp);
    }
    if(vis3[x + y] != -1)
    {
        tmp = vis3[x + y];
        if(n - x < y)
            tmp += n - x;
        else
            tmp += y;
        res = min(res, tmp);
    }
    if(vis4[x + y] != -1)
    {
        tmp = vis4[x + y];
        if(x < m - y)
            tmp += x;
        else
            tmp += m - y;
        res = min(res, tmp);
    }
    if(res != INF)
        return res;
    else
        return -1;
}
int main()
{
    int px, py, dir, tx, ty;
    long long tmp, time, ans;   //时间需要用long long
    while(cin >> n >> m >> k)
    {
        memset(vis1, -1, sizeof(vis1));         //全部都设为-1
        memset(vis2, -1, sizeof(vis2));
        memset(vis3, -1, sizeof(vis3));
        memset(vis4, -1, sizeof(vis4));
        px = py = time = 0; dir = 1;        //dir为方向,px/py为当前点
        while(true)         //模拟所有起点的过程
        {
            if(dir == 1)
            {
                vis1[px - py +N] = time;        //因为px-py可能小于0,所以加上N
                tmp = min(n - px, m - py);      //是先到最上边的墙,还是先到最右边的墙
                px += tmp;          //把达到墙需要的时间加上
                py += tmp;
                time += tmp;
                if(px == n && py == m)  //如果进入右上定点则结束
                    break;
                else if(px == n)
                    dir = 3;
                else if(py == m)
                    dir = 4;
            }
            else if(dir == 2)
            {
                vis2[px - py + N] = time;
                tmp = min(px, py);
                px -= tmp;
                py -= tmp;
                time += tmp;
                if(px == 0 && py == 0)
                    break;
                else if(px == 0)
                    dir = 4;
                else if(py == 0)
                    dir = 3;
            }
            else if(dir == 3)
            {
                vis3[px + py] = time;
                tmp = min(px, m - py);
                px -= tmp;
                py += tmp;
                time += tmp;
                if(px == 0 && py == m)
                    break;
                else if(px == 0)
                    dir = 1;
                else if(py == m)
                    dir = 2;
            }
            else if(dir == 4)
            {
                vis4[px + py] = time;
                tmp = min(n - px, py);
                px += tmp;
                py -= tmp;
                time += tmp;
                if(px == n && m == 0)
                    break;
                else if(px == n)
                    dir = 2;
                else if(py == 0)
                    dir = 1;
            }
        }
        while(k--)
        {
            scanf("%d%d", &tx, &ty);
            ans = slove(tx, ty);
            printf("%I64d\n", ans);
        }
    }
    return 0;
}




/*
                   _ooOoo_
                  o8888888o
                  88" . "88
                  (| -_- |)
                  O\  =  /O
               ____/`---'\____
             .'  \\|     |//  `.
            /  \\|||  :  |||//  \
           /  _||||| -:- |||||-  \
           |   | \\\  -  /// |   |
           | \_|  ''\---/''  |   |
           \  .-\__  `-`  ___/-. /
         ___`. .'  /--.--\  `. . __
      ."" '<  `.___\_<|>_/___.'  >'"".
     | | :  `- \`.;`\ _ /`;.`/ - ` : | |
     \  \ `-.   \_ __\ /__ _/   .-` /  /
======`-.____`-.___\_____/___.-`____.-'======
                   `=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         I have a dream!A AC deram!!
 orz orz orz orz orz orz orz orz orz orz orz
 orz orz orz orz orz orz orz orz orz orz orz
 orz orz orz orz orz orz orz orz orz orz orz

*/


扩展欧几里得解法:

把矩形对称展开,最后小球在横纵坐标均为 maxx=mn/gcd(m,n) 处被吸收。 
原坐标为 (x,y) 的小球经过轴对称展开,其坐标为 (2kn±x,2sm±y),k,s .要使得在吸收前经过点,则坐标必须在线段(0, 0)到(maxx, maxx)之间。 
即要解方程 2kn±x=2sm±y ,求为正最小的 2kn±x 。利用扩展欧几里得解方程。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL n, m;
LL exgcd(LL a, LL b, LL &x, LL &y)
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    LL r=exgcd(b, a%b, y, x);
    y-=a/b*x;
    return r;
}
LL get_min_answer(LL a, LL b, LL c, LL &x, LL &y)
{
    LL gcd=exgcd(a, b, x, y);
    if(c%gcd) return -1;
    LL s=b/gcd;
    x*=c/gcd;
    if(s<0) s=-s;
    x=(x%s+s)%s;
    return 0;
}
LL solution(LL dx, LL dy, LL Max)
{
    LL x, y;
    if(get_min_answer(2*n, -2*m, dy-dx, x, y)==-1)
        return Max+1;
    LL tx=2*x*n+dx;
    if(tx<0||tx>Max) return Max+1;
    return  tx;
}
LL solve(LL x, LL y)
{
    LL gcd=__gcd(n, m);
    LL Max=n*m/gcd, ans=Max+1;
    ans=min(ans, solution(-x, -y, Max));
    ans=min(ans, solution(x, -y, Max));
    ans=min(ans, solution(-x, y, Max));
    ans=min(ans, solution(x, y, Max));
    if(ans==Max+1) return -1;
    return ans;
}
int main()
{
    LL k, x, y;
    while(cin>>n>>m>>k)
    {
        while(k--)
        {
            cin>>x>>y;
            cout<<solve(x, y)<<endl;
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值