每日c/c++题 备战蓝桥杯(小球反弹)[镜像思路求解,最小公倍数]
思路:
- 错解:对于这道题而言,有的同学会选择用计算每次碰撞的坐标,直到坐标等于原点的方法来做,但这种方法实现起来比较繁琐,并且由于碰撞点的坐标有可能是浮点数,而浮点数会丢失精度,如果碰撞次数非常多,那么精度损失会无法承受
- 正解:以宏观的角度观察题目,看看有什么特点。下面分享两种方法。
方法一:运动分解求解(最大公约数)
详细请看我之前的博客。点击链接
方法二:镜像求解(最小公倍数)
原理:
对于一次长边的碰撞,可以看成沿长边翻转后,继续向镜像中前进,如下图
同理,对于一次短边的碰撞,也可以看成沿短边翻转后,继续向镜像中前进,如下图
思路:
有了上面的原理,我们可以推出:当小球一直沿着镜像中“直线”出发,第一次到达某个镜像原点的时候,就是正解的情况。
我们继续观察镜像原点坐标的规律,如下图红点
我们可以发现对于每个镜像原点的坐标,满足以下条件:
(x,y)∈ {x是2X的倍数,y是2Y的倍数}
其中X是题目长方形的长,Y是长方形的宽
有了上面的发现后,我们只需要找到同时符合x,y方向上分运动的路程分别满足以上条件的时间t即可,找到了时间 t,我们就可以用以下公式来求解答案
S = t * V(和)
其中:
- t为小球返回原点所需总时间
- V(和) 为合速度
- S 为题目要求的总路程
需要注意的是,题目给的分速度是比的形式,但我们仍然可以看作dx是15,dy是17
暴力算法实现步骤
- 遍历单位时间 t
- 找到同时符合x,y方向坐标的的t
- 输出结果保留2位小数
注意事项:
t可以为单位时间的原因(整数)是,互质的两个数只有乘以一个整数,结果才能都是整数,dx和dy互质,而镜像原点xy坐标都为整数,也就是说到达原点的情况下时间t一定是整数,固可用整数遍历t。
暴力代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int X=343720,Y=233333,dx = 15,dy =17;
int t=1;
for(t=1;;++t)
{
if((dx*t) % (2*X) == 0 && (dy*t) % (2*Y) == 0)
{
break;
}
}
double ans = t * sqrt(15*15+17*17);
printf("%.2lf", ans);
return 0;
}
输出结果
1100325199.77
算法优化
对于某一分量如x方向,位移必须是2X的整数倍,则第一次碰撞在最小公倍数lcm(dx,2X)上(dx为x的速率,X为宽),第二次在2*lcm(dx,2X)上,以此类推,则时间步长可以设置为lcm(dx,2X)/dx,即路程除以速度,后面遍历按照x或y方向步长增倍即可。
优化后的代码
#include<bits/stdc++.h>
using namespace std;
int gcd(int x,int y)
{
int tem = x%y;
while(x%y)
{
x=y;
y=tem;
tem = x%y;
}
return y;
}
int lcm(int x,int y)
{
return x*y/gcd(x,y);
}
int main() {
int X=343720,Y=233333,dx = 15,dy =17;
int tem=lcm(dx,2*X)/dx;
int t=1;
for(t=tem; ;t += tem)
{
if((dx*t) % (2*X) == 0 && (dy*t) % (2*Y) == 0)
{
break;
}
}
double ans = t * sqrt(15*15+17*17);
printf("%.2lf", ans);
return 0;
}
总结
通过上述方法,我们了解了小球反弹的另一种解题思路:镜像思路,我们可以准确计算出小球运动的总路程,并保留两位小数作为最终答案。这种方法避免了浮点数精度损失的问题,同时简化了计算过程,提高了效率。