[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;
}

适合初学辗转相除的人做。推荐!

posted @ 2022-05-04 00:07  dbxxx  阅读(73)  评论(0编辑  收藏  举报