鱼跃龙门——扩展欧几里得与唯一分解定理的巧妙运用

本文针对一个数学问题进行了详细的解析,该问题是关于一个胖头鱼在若干龙门间跳跃,寻找返回起点所需最小时间的问题。通过数学变换,将问题转化为求特定形式的方程解,并采用扩展欧几里得算法进行求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给定一个正整数 nnn,一共有 nnn 座龙门,跳过第 j(j<n)j (j < n)j(j<n) 座龙门将会到达第 j+1j+1j+1 座龙门前,特殊地,跳过第 nnn 座龙门后将会到达第 111 座龙门前。
胖头鱼一开始在第一座龙门前,接下来,第 iii 个时刻内它会向前跳 iii 次,每次跳过 111 座龙门,求最小的正整数 xxx 满足第 xxx 个时刻结束后胖头鱼恰好会回到起点。

输入格式

第一行一个整数 TTT,表示数据组数。
接下来 TTT 行,每行一个整数 nnn,表示一共有 nnn 座龙门。

输出格式

一共 TTT 行,每行一个整数 xxx,表示答案。

输入样例1

5
2
4
6
8
10

输出样例1

3
7
3
15
4

样例解释1

n=10n=10n=10 的样例解释:
一开始在 111 的前面也就是位置 000
第一时刻跳一步到 (0+1) mod n=1(0+1)\bmod n = 1(0+1)modn=1
第二时刻跳两步到 (1+2) mod n=3(1+2)\bmod n = 3(1+2)modn=3
第三时刻跳三步到 (3+3) mod n=6(3+3)\bmod n = 6(3+3)modn=6
第四时刻跳四步到 (6+4) mod n=0(6+4)\bmod n = 0(6+4)modn=0
第四时刻后恰好跳回原点 000,所以答案为 444

输入样例2

10
200479710
1041705379
766770747
257088468
877586977
86834214
757618747
884911150
388001368
494728090

输出样例2

30464759
9427197
74899308
9020648
877586976
43417107
96416647
212378675
43718463
36697240

数据范围

对于 10%10\%10% 的数据 1≤T≤10,1≤n≤101 \leq T \leq 10, 1 \leq n \leq 101T10,1n10
对于全部的数据 1≤T≤100,1≤n≤10121 \leq T \leq 100, 1 \leq n \leq 10^{12}1T100,1n1012

题目解答

这题初看起来很像扩欧,像我这样的蒟蒻就像直接套板子了。但可惜的是,虽然这道题需要使用扩欧,可并不能直接使用,而是要经过一系列复杂的变换才可以。那我们就开始题解部分:

  • 首先,根据求和公式,容易发现就是求式子 n∣12×x×(x+1)n \mid \frac{1}{2} \times x \times (x + 1)n21×x×(x+1) 的最小正整数解 xxx
  • 接着两边同时乘二,得到 2n∣x×(x+1)2n \mid x \times (x + 1)2nx×(x+1)

显然,我们不可能使用二次公式计算,所以要换一种方法。由于 xxxx+1x + 1x+1 互质,所以我们可以尝试对 2n2n2n 进行质因数分解:
2n=∏i=1spiki 2n = \prod_{i = 1}^{s} p_i^{k_i} 2n=i=1spiki

  • 其中每个 pip_ipi 都是质数,必定与 pj(i≠j)p_j (i \neq j)pj(i=j) 互质,所以只要对它的每一个质因子进行枚举子集即可得到两个乘积为 nnn 且互质的数

虽然枚举两个数的乘积可以被 nnn 整除且互质,但它们的差值不一定为 111。并不一定能构成 x×(x+1)x \times (x + 1)x×(x+1) 的形式,所以这时候,扩展欧几里得就派上了用场。若我们当前在质因数中枚举出的两个数为 a,ba, ba,b,就可以把它转换成以下式子:
ap−bq=1 ap - bq = 1 apbq=1

  • 在这里,我们将 aaabbb 扩大一定倍数后去满足它们的差为 111 这一性质。apapap 表示的就是 x+1x + 1x+1,而 bqbqbq 相对应的就为 xxx 了,恰好就是一个扩欧标准的方程
  • 由于 n∣a×bn \mid a \times bna×b ,那么 n∣ap×bqn \mid ap \times bqnap×bq,方程里的值满足能将 nnn 整除。

不过还要注意两点:

  • 因为扩展欧几里得求解的是 ax+by=cax + by = cax+by=c,与上面的方程符号相反,而题目要求的是最小的正整数,所以求解的应该是 bbb 的最大负整数解
  • 由于题目里的数据较大,直接分解质因数可能超时,所以应该使用筛法加速这一过程

AC代码

#include <bits/stdc++.h>
using namespace std;
long long t, n, ans, pos, x, y, idx, cntp[1000005], primes[1000005];
bool nPrime[1000005];

void euler(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!nPrime[i])
            primes[++pos] = i;
        for (int j = 1; j <= pos && i * primes[j] <= n; j++)
        {
            nPrime[i * primes[j]] = true;
            if (i % primes[j] == 0)
                break;
        }
    }
}

long long exgcd(long long a, long long b, long long &x, long long &y)
{
    if (!b)
    {
        x = y = 1;
        return a;
    }
    long long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

void dfs(int cur, long long a, long long b)
{
    if (cur > idx)
    {
        exgcd(a, b, x, y);
        y = -y % a; // 取最大负整数解
        if (y <= 0) // 解仍然可能小于等于0,应该加上a
            y += a;
        ans = min(ans, y * b); // y * b就为答案x,用结果更新ans
        return;
    }
    dfs(cur + 1, a * cntp[cur], b);
    dfs(cur + 1, a, b * cntp[cur]);
}

int main()
{
    euler(1e6);
    scanf("%lld", &t);
    while (t--)
    {
        scanf("%lld", &n);
        n <<= 1, ans = LONG_LONG_MAX, idx = 0;
        for (int i = 1; i <= pos && primes[i] * primes[i] <= n; i++) // 质因数分解,使用筛法加速
            if (n % primes[i] == 0)
            {
                cntp[++idx] = 1; // cntp数组用来存每个pi^ki
                while (n % primes[i] == 0)
                {
                    n /= primes[i];
                    cntp[idx] *= primes[i];
                }
            }
        if (n > 1)
            cntp[++idx] = n;
        dfs(1, 1, 1);
        printf("%lld\n", ans);
    }
    return 0;
}

本期博客就到这里了,若注解有误,还请跟为大佬多多指教。
如果觉得写得好的话,还可以点赞 + 收藏^⌣^\hat{} \smile \hat{}^^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值