Intel Code Challenge Final Round C. Ray Tracing 拓展欧几里得或模拟 (好题)

7 篇文章 0 订阅
6 篇文章 0 订阅

题目地址:http://codeforces.com/contest/724/problem/C

题意:有一个小球在一个n*m的矩阵中,矩阵中有k个点,问这个小球从 (0,0) 点以 (1,1) 的速度出发,遇到边界就反射,直到遇到矩形四个角的一个停止的过程中,小球第一次经过这k个点的时刻分别是多少。

比赛的时候用模拟过了这道题,不过感觉写起代码真累,后来看到了这篇博客:http://www.cnblogs.com/qscqesze/p/5942111.html。感觉数学方法真是神奇,如果比赛的时候可以很快想到数学方法的话,不仅代码写起来快,出错的概率还会比模拟小。但是理解了数学方法后想了一想,即使在最坏情况下,两种方法的复杂度也只是差了一个常数。(数学方法是 O(k) ,我模拟的思路是枚举每次的撞击点,而撞击点一定在矩形边界上,所以说撞击点也不会超过 2(n+m) 个,结合stl的运行时间,综合一下最坏也在 O(2(n+m)logk) 左右,但能达到这个最坏情况的数据是显然是造不出来的)。模拟的话思路可以很快想到,所以说两种方法各有优劣吧。
先说一下数学方法的思路和代码,最后也放一放我的模拟代码…………

思路:
对每个点分别考虑:小球到该点需要的时间。
把这个问题简化成一个坐标系下的问题,一个点从0点出发,在这个点回到0点之前会经过两次a点,设经过a点话费的时间为t,有: t=x n(tn)=x ,结合小球的运动周期是 2n ,上面两式可化简为 t=2kn±x 。再把y放进来,就得到了一个方程组:

t=2k1n±xt=2k2m±y
显然,这个方程组有四种情况,需要我们讨论。我们先取
t=2k1n+xt=2k2m+y
这个情况讨论。
联立方程,得: k12nk22m=yx 该方程可用扩展欧几里得求解,然后我们可以得到无数组符合方程的时间t,但是在题意下,小球经过的点最多为 lcm(n,m) (不懂的话自己画图看看)。所以我们可以取到t的最小整数解。
大体思路就是:先确定小球结束运动的时间,然后枚举这个方程的四种情况,要么无解,要么输出最小的正整数解
然后一些细节也说一下……
* 其实前面说的四个方程组代表的是小球速度的四种状态(正向45°,反向45°,正向135°,反向135°),一个点,如果可以被经过的话,则两个方程组都会出解(忽略小球遇到四个角中的一个就要停止这个条件,一个点会被不同的速度状态经过两次)。
* 但是有一个解可能是超时的,举个例子: (3,3) 的矩阵,点是 (2,2) ,则第一次经过的时间是2s,第二次是4s,其实没有第二次,因为3s时就停止运动了。但是有一个方程组会解出4这个答案,而且之前我们认为 小球经过的点最多为 lcm(n,m) ,用时限3s去求最小正整数解,本来应该出4s的解的方程组却会得出1s的解!
* 细细一想,运用小学时解决过河问题的经验,矩形中的某点有另外三个对称点,我们不妨把矩形的长和宽变为原来的两倍,解 (x,y) (2nx,y) (x,2my) (2nx,2my) ,这四个方程组(方程中的n,m都指矩形最初的面积),这样这三个对称点都被包含在矩形内部了
* 取有解情况中的最小解,就是第一次相遇的时间

详见代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL INF=10000000000000LL;
LL n,m,t,GCD,X,Y;

LL exgcd(LL a,LL b,LL &x,LL &y){
    if(b==0){
        x=1;y=0;
        return a;
    }
    LL e=exgcd(b,a%b,x,y);
    LL tmp=x;
    x=y;
    y=tmp-(a/b)*y;
    return e;
}
LL solve(int x,int y){
    if((y-x)%GCD) return INF;
    LL xx=(y-x)/GCD*X;
    xx=xx%t*n%t+x;
    xx=(xx%t+t)%t;
    if(xx==0) xx=t;
    return xx;
}

int main(){
    int k;
    LL x,y,ans;
    while(~scanf("%I64d%I64d%d",&n,&m,&k)){
        GCD=exgcd(2*n,2*m,X,Y);
        n*=2,m*=2;
        t=n/GCD*m;
        while(k--){
            scanf("%I64d%I64d",&x,&y);
            ans=min({solve(x,y),solve(n-x,y),solve(x,m-y),solve(n-x,m-y)});
            if(ans<t) printf("%I64d\n",ans);
            else puts("-1");
        }
    }
}

模拟代码:
用set存下从每个撞击点出发到下一次撞击前能遇到的点,开始枚举撞击点,然后不断拿出,记录答案……

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
typedef long long LL;
const int MAXN=1e5+10;
struct Node{
    int x,y,num;
    Node(int _x,int _y,int _num){x=_x,y=_y,num=_num;};
    bool operator<(const Node& rhs) const{
        return x<rhs.x||(x==rhs.x&&y<rhs.y);
    }
};set<Node> S[4*MAXN];int n,m,k,x,y;
LL ans[MAXN];

int type;
LL tm;
bool flag;
void insert(int x1,int y1,int clk){
    if(x1==y1) S[0].insert((Node){x1,y1,clk});
    else if(x1>y1) S[x1-y1].insert((Node){x1,y1,clk});
    else S[MAXN+y1-x1].insert((Node){x1,y1,clk});

    S[2*MAXN+x1+y1].insert((Node){x1,y1,clk});
}
inline int tim(const Node& rhs){
    return abs(x-rhs.x);
}
void check(){
    int tag;
    if(type==0){
        if(x==y) tag=0;
        else if(x>y) tag=x-y;
        else tag=MAXN+y-x;
        for(Node i:S[tag]){
            ans[i.num]=tm+tim(i);
            if(S[2*MAXN+i.x+i.y].count(i))
                S[2*MAXN+i.x+i.y].erase(i);
        }
        S[tag].clear();
    }

    else{
        for(Node i:S[2*MAXN+x+y]){
            ans[i.num]=tm+tim(i);
            if(i.x==i.y) tag=0;
            else if(i.x>i.y) tag=i.x-i.y;
            else tag=MAXN+i.y-i.x;
            if(S[tag].count(i))
                S[tag].erase(i);
        }
        S[2*MAXN+x+y].clear();
    }
}
void eva(){
    if(type==0){
        if(x==0||y==0){
            int d1=n-x,d2=m-y;
            x+=min(d1,d2);
            y+=min(d1,d2);
            tm+=min(d1,d2);
        }
        else if(x==n||y==m){
            int d1=x,d2=y;
            x-=min(d1,d2);
            y-=min(d1,d2);
            tm+=min(d1,d2);
        }
        type=1;
    }
    else{
        if(x==0||y==m){
            int d1=n-x,d2=y;
            x+=min(d1,d2);
            y-=min(d1,d2);
            tm+=min(d1,d2);
        }
        else if(x==n||y==0){
            int d1=x,d2=m-y;
            x-=min(d1,d2);
            y+=min(d1,d2);
            tm+=min(d1,d2);
        }
        type=0;
    }
    if((x==0||x==n)&&(y==0||y==m)) flag=true;
}

int main(){
    scanf("%d%d%d",&n,&m,&k);
    MS(ans,-1);
    for(int i=0;i<k;++i){
        scanf("%d%d",&x,&y);
        insert(x,y,i);
    }
    type=tm=x=y=0;
    while(true){
        check();
        eva();
        if(flag) break;
    }
    for(int i=0;i<k;++i) printf("%I64d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值