题目地址: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−(t−n)=x
,结合小球的运动周期是
2n
,上面两式可化简为
t=2kn±x
。再把y放进来,就得到了一个方程组:
联立方程,得: k1∗2n−k2∗2m=y−x 该方程可用扩展欧几里得求解,然后我们可以得到无数组符合方程的时间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) , (2∗n−x,y) , (x,2∗m−y) , (2∗n−x,2∗m−y) ,这四个方程组(方程中的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]);
}