https://hihocoder.com/problemset/problem/1584
题意:在n*m的网格上,一颗小球左上方出发往右下的格子走,碰到边界就反弹,当小球到达角落就停止运动。求小球只经过一次的格子的数量。
题解:
第一步:如图(3*4为例),可以把每次反弹后的线路展开,每次反弹就增加m-1(碰到上下边界)或者n-1(碰到左右边界)。因为小球是45度方向,所以最后展开一定是正方形。于是可以列出不定方程(m-1)*x+m=(n-1)*y+n。其中x和y可以通过扩展欧几里德求出最小正数解。(m-1)*x+m就是小球经过的格子总数(经过同一个两次的算两次)。
第二步:然后要减去经过两次的格子。x就是左右反弹次数之和,y就是上下反弹次数之和。相交的就是经过两次的要减去。可以从样例图的交点看出,数量就是x*y(结合样例图与反弹次数有关,因为每次反弹会与另一个方向的路线有相交)。
那么答案就是(m-1)*x+m-x*y。
画的有点丑。最后是要变成一个正方形。
样例图
代码:
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<vector>
#include<string>
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<iostream>
#define debug cout<<"aaa"<<endl
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define lson l,mid,root<<1
#define rson mid+1,r,root<<1|1
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;
const int N = 100000 + 5;
const int mod = 1000000000 + 7;
//扩展欧几里德
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,x,y);
LL t=x;
x=y;
y=t-a/b*y;
return r;
}
int main(){
int n,m;
LL x,y,gcd,temp,num;
while(~scanf("%d%d",&n,&m)){
gcd=exgcd(m-1,1-n,x,y);
x=x*(n-m)/gcd;
temp=(1-n)/gcd;
if(temp<0){
temp=-temp;
}
//扩欧出来的解可能是负的,要求出最小正数解
if(x<0){
x=x+(-x/temp+1)*temp;
}
else{
x%=temp;
}
y=((m-1)*x+m-n)/(n-1);
printf("%lld\n",(m-1)*x+m-x*y);
}
return 0;
}