Problem
hihocoder.com/problemset/problem/1584
vjudge.net/problem/HihoCoder-1584
Meaning
一个球在 n * m 的方格图里弹来弹去,开始在左上角,初始方向是向右下角,一直弹到四个角落之一才停下。问球在弹的过程中,经过多少个只经过一次的点
Analysis
dalao 的解法简直是玄学,我尝试说明一下…
总的思路是用总的路径长度减去两倍那些经过两次的点数(就是图中那些交叉所在的点,点最多被经过两次)。
把路径拆成一段段的,如下图圈出两段:
首先来计算段长:
设段长为 x,打竖一共有 r 条,打横有 c 条,那么:
(n - 1) / (x - 1) = r
(m - 1) / (x - 1) = c
以题目的图为例,算 r。先忽略第一行(红色圈那行),然后把长度为 3 的段首尾相接地排下来(两段的一个端点共存于一个格),看作是长度为 2 的段首尾不相接地排(蓝色圈出的段),于是段数就是(n - 1) / (x - 1)
。打横类似。
因为 r 和 c 都要是整数,于是x - 1 = gcd(n - 1, m - 1)
(不知道为什么是最大的公约数…)
总的段数就是r * c
,而总路程是r * c * (x - 1) + 1
。同样的,因为段是首尾相接地连在一起,而连接的地方会算重一个格,所以把段都看成是长为x - 1
,但由于最开始的那段(最左上角)没有算重,要补回那个端点。
然后算交叉的点数。观察图形,交叉所在行的最左边,是一个“凹”或者一个“凸”,所在列的最上方也是:
行的凹凸数是r - 1
,列的凹凸数是c - 1
,然后(r - 1) * (c - 1)
刚好就是交叉的点的数目的两倍(玄学…)。减两次是因为这些交叉点在算总路程的时候被算了两次(被经过两次),所以要减两次。
“凹凸行”和“凹凸列”相交的那些点,有些是有交叉,而另外一些是没经过的点,为什么是对半分?我们对部分交叉作一点偏移,移成一列全是交叉、一列全是未经过这样的布局(或是按行来移),而刚好交叉列和未经过列是对半分(列凹凸数是偶数,好像行凹凸数和列凹凸数至少有一个是偶数,但不知道为什么),所以,相乘就是交叉点数的两倍。
Code
#include <iostream>
using namespace std;
long long gcd(long long a, long long b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
long long n, m;
while(cin >> n >> m)
{
long long len = gcd(n - 1, m - 1) + 1, // 段长
r = (n - 1) / (len - 1),
c = (m - 1) / (len - 1);
long long ans = r * c * (len - 1) + 1; // 总路程
ans -= (r - 1) * (c - 1); // 减去交叉
cout << ans << '\n';
}
return 0;
}