[luogu p2090] 数字对
\(\mathtt{Link}\)
P2090 数字对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
\(\mathtt{Description}\)
数字对 \((a, b)\),一次操作可以使得其变为 \((a, a + b)\) 或 \((a + b, b)\).
对于一个数字对 \((1, 1)\),问至少多少次操作可以使数对中的任意一个数变为 \(n\).
\(\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}\)
- \(1 \le n \le 10^6\)
\(\mathtt{Solution}\)
为什么不 % 1e9 + 7??为什么为什么为什么
本题中的数对是对称性的。因此不妨单独考虑让第一个数变为 \(n\).
首先我们可以想到这样一个性质,最少步数达到目的的数对 \((n, i)\) 肯定有 \(i \le n\)。
因为如果可达到的数对 \((n, i)\) 中 \(i > n\),那么一定存在一个数对 \((n, i - n)\) 比 \((n ,i)\) 早一步到达。
也就是说 \(i\) 的范围就是 \(1 \le i \le n\),考虑大胆枚举更新答案。
那么对于一个数对 \((n, i)\),我们发现它的上一步一定是 \((n - i, i)\) 变的。这启示我们,对于一个数对,它的上一步是唯一确定的。那么只要一步一步向前推即可。
考虑加速。
我们注意到如果 \(n - i > i\),那么 \((n - i, i)\) 的上一步一定还是 \((n - 2i, i)\)。
也就是对于每一次数对,我们可以考虑把大的不断减去小的,直到减不下去。
具体来说就是 \((a, b)\)(\(a > b\))通过 \(\left\lfloor \dfrac{a}{b} \right\rfloor\) 次操作可以变为 \((a \bmod b, b)\),这个时候左边一定比右边小了。
那么我们考虑调换,变成 \((b, a \bmod b)\). 这个时候,左边又一定比右边大,再考虑一直欺负左边那个。
聪明的你一定看出来了,这其实就是辗转相除法的思想。进行一个简单的递归即可。
那么什么时候无解呢?如果其中任何一个时候 \(a \bmod b = 0\) 就无解了。
其实不难发现,其中出现 \(a \bmod b = 0\),当且仅当 \(\gcd(a, b) \ne 1\)。那么在入口处判断一下就可以。
聪明的你可能会发现了,最后的数对 \((n, i)\),\(i\) 连 \(n\) 都取不到。\(i\) 其实枚举到 \(n - 1\) 就可以了(雾)
\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
\(\operatorname{O}(n\log n)\)
\(\mathtt{Code}\)
#include <iostream>
#include <cstdio>
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = false;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag) return x;
return ~(x - 1);
}
int solve(int a, int b) {
if (a == 1 || b == 1)
return a + b - 2;
return a / b + solve(b, a % b);
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int main() {
int n = read(), ans = 0x3f3f3f3f;
for (int i = 1; i < n; ++i)
if (gcd(n, i) == 1)
ans = std :: min(ans, solve(n, i));
printf("%d\n", ans);
return 0;
}
适合初学辗转相除的人做。推荐!