2025-02-11 08:43阅读: 10评论: 0推荐: 0

威尔逊定理

引入(从模板题开始)


题目:给定 n,p,求 n!modp 的值,保证 p 是质数。

思考


先回忆一下威尔逊定理的内容。威尔逊定理说的是,当p是一个质数的时候,\((p-1)! ≡ -1 mod p。\)也就是说,当p是质数时,\((p-1)!4\)的结果对p取模等于\(-1\),或者说\(p-1\),因为\(-1\)\(p-1\)在模p下是同余的。
那现在问题是要计算n! mod p,其中n可能比p小,或者等于p,或者更大?比如说,当n大于等于p的时候,n!里肯定会包含p这个因子,所以这时候n! mod p应该是0,因为p是其中的一个乘数,模p的话结果自然是0。那这时候问题就简单了,如果n>=p的话,直接返回0就行了。

那如果n比p小的话,这时候怎么办呢?这时候我们需要计算n!的乘积,然后对p取模。但这样的话,如果p很大而n也比较大,直接计算可能会比较耗时,比如n是1e5,p是1e9+7这样的质数,这时候直接算阶乘然后取模可能还是可以处理的,但有没有更高效的方法呢?或者题目是否有其他要求?
当n < p的时候,我们需要找到一个方法,利用威尔逊定理来计算n! mod p的值。

比如,假设p是质数,当\(n < p\)时,\(n! = 1×2×3×…×n\)。这时候直接计算的话,可能比较直接,但有没有办法将n!和(p-1)!联系起来呢?因为根据威尔逊定理,\((p-1)! ≡ -1 mod p\),所以我们可以考虑如何将\(n!和(p-1)!\)联系起来。

比如,当n < p时,我们可以将(p-1)!写成n! × (n+1)(n+2)...(p-1)。因此,(p-1)! ≡ n! × (n+1)(n+2)...(p-1) ≡ -1 mod p。所以如果我们能够找到(n+1)(n+2)...(p-1) mod p的逆,那么就可以用这个等式来解出n! mod p。

也就是说,\(n! ≡ -1 × [(n+1)(n+2)...(p-1)]^{-1} mod p\)。这里的逆元是模p的逆元,因为p是质数,所以每个数都有逆元。

但是,当n < p的时候,比如\(p=7,n=4\),那么\((p-1)! = 6! = 720,\)\(4! =24,\)这时候\((p-1)! =4! ×5×6。\)根据威尔逊定理,\(6! ≡-1 mod7\)。所以\(24 ×5×6 ≡-1 mod7\)。计算的话,\(5×6=30 mod7=2,24 mod7=3\),所以\(3×2=6≡-1 mod7\),确实符合。所以这时候\(4!≡3 mod7,\)而按照上面的式子,\(4!≡ (-1) × (5×6)^{-1} mod7\)。其中\(5×6=30≡2 mod7\)。所以逆元是\(2^{-1} mod7\),也就是\(4\),因为\(2×4=8≡1 mod7。\)所以\(4!≡ (-1) ×4 mod7 → -4 mod7=3,\)正确。

综上:所以这个方法是可行的。那现在问题转化为如何计算\((n+1)(n+2)...(p-1)\)的乘积$mod p4,然后求其逆元,再用威尔逊定理得到n!的值。

优化


考虑当k从n+1到p-1时,可以将这些数表示为p - m,其中m从1到p-1 -n。

例如:

当p=7,n=4,那么\(n+1=5=7-2,n+2=6=7-1。\)所以乘积\(5×6= (7-2)(7-1) ≡ (-2)(-1) mod7=2×1=2\)。这时候,乘积等于\((-1)^2 ×2×1=2\)。同样,原式中的乘积确实是\(2 mod7\)
所以,一般来说,当k = p -m,那么k ≡-m modp。所以乘积从n+1到p-1的各个数,可以转化为乘积_{m=1}^{p-1 -n} (-m) modp,也就是 (-1)^{p-1 -n} × (p-1 -n)! modp。例如,当p=7,n=4,则p-1-n=2,所以乘积是 (-1)^2 ×2! =1×2=2,与之前的例子一致。

所以,乘积(n+1)(n+2)...(p-1) ≡ (-1)^{(p-1 -n)} × (p-1 -n)! modp。

这样,原来的等式可以写成:

\(n! × (-1)^{p-1 -n} × (p-1 -n)! ≡-1 modp\)

接下来,我们可以将这个式子重新排列,解出n!。于是:

\(n! ≡ (-1) × [ (-1)^{p-1 -n} × (p-1 -n)! ]^{-1} modp\)

这可以进一步简化:

\(n! ≡ (-1) × (-1)^{- (p-1 -n)} × [ (p-1 -n)! ]^{-1} modp\)

因为$ [ (-1)^k ]^{-1} = (-1)^{-k} modp$,而 \((-1)^{-k} = (-1)^k\),因为\((-1)\)的逆是它自己,所以 \((-1)^{-k} = [ (-1)^{-1} ]^k = (-1)^k\)

比如,当p是奇质数时,-1的逆元是-1,因为 (-1)×(-1)=1。所以,\([ (-1)^k ]^{-1} = (-1)^k\)。例如,k=3时,\((-1)^3=-1\),它的逆是-1,同样等于(-1)^3。所以,是的,\([ (-1)^k ]^{-1} = (-1)^k\)。所以上面的式子变为:

\(n! ≡ (-1) × (-1)^{p-1 -n} × [ (p-1 -n)! ]^{-1} modp\)

合并指数:

\((-1) × (-1)^{p-1 -n} = (-1)^{1 + p-1 -n} = (-1)^{p -n}\)

所以:

\(n! ≡ (-1)^{p -n} × [ (p-1 -n)! ]^{-1} modp\)

这时候,我们需要计算的是(p-1 -n)!的逆元modp,以及(-1)的幂次。

现在,令\(m = p -1 -n\),那么m!的逆元modp。这个时候,因为\(m < p\)(因为\(n >=0\),所以 \(p-1 -n <=p-1)\),所以m!和p互质,因此逆元存在。

所以,现在的问题转化为如何计算m!的逆元modp,其中m =p-1 -n。这可能比较高效,因为m可能比n小,或者可能更小,具体取决于n的大小。

例如,当n比较接近p的时候,m= p-1 -n会比较小,这时候计算m!的逆元可能更容易。或者当n比较小时,m可能接近p-1,这时候可能需要用到威尔逊定理或其他方法。

但不管怎样,这可能是一个可行的路径。例如,当\(m= p-1 -n\),则:

\(n! ≡ (-1)^{p -n} × (m! )^{-1} modp\)

所以,这个式子是正确的。

现在,回到如何计算m!的逆元。当m较小时,可以预先计算阶乘及其逆元。例如,如果我们能计算出m! modp,然后用费马小定理求逆元,因为p是质数,所以逆元就是\(m!^{p-2} modp\)。或者,当m较大的时候,可能这种方式效率较低,但题目可能允许我们直接使用这样的方法。

不过,现在的问题可能要求我们编写一个算法,能够处理不同的n和p的情况。例如,当n >=p时,返回0;当n <p时,利用上述公式计算n! modp。

总结:


1. 如果n >=p,返回0。

2. 否则,计算m = p-1 -n。

3. 计算m! modp。

4. 计算m!的逆元modp,即pow(m!, p-2, p)。

5. 计算符号因子 (-1)^{p-n} modp。这等于1如果p-n是偶数,否则等于-1。

6. 结果为符号因子乘以逆元,然后取模p。

上代码


#include <iostream>
using namespace std;

// 快速幂函数,用于计算 a^b mod p
long long fast_pow(long long a, long long b, long long p) {
    long long result = 1;
    a = a % p;
    while (b > 0) {
        if (b & 1) { // 如果 b 是奇数
            result = (result * a) % p;
        }
        a = (a * a) % p; // a 平方
        b = b >> 1; // b 除以 2
    }
    return result;
}

// 计算 n! mod p
long long factorial_mod(long long n, long long p) {
    if (n >= p) { // 如果 n >= p,n! 必定包含 p,结果为 0
        return 0;
    }

    // 计算 m = p - 1 - n
    long long m = p - 1 - n;

    // 计算 m! mod p
    long long m_factorial = 1;
    for (long long i = 1; i <= m; i++) {
        m_factorial = (m_factorial * i) % p;
    }

    // 计算 m! 的逆元,利用费马小定理
    long long inv_m_factorial = fast_pow(m_factorial, p - 2, p);

    // 计算符号因子 (-1)^(p - n)
    long long sign = (p - n) % 2 == 0 ? 1 : -1;

    // 计算 n! mod p
    long long result = (sign * inv_m_factorial) % p;
    if (result < 0) { // 如果结果为负数,加上 p 使其变为正数
        result += p;
    }

    return result;
}

int main() {
    long long n, p;
    cin >> n >> p;

    long long result = factorial_mod(n, p);
    cout << result << endl;

    return 0;
}


谢谢观看

本文作者:ccgc718

本文链接:https://www.cnblogs.com/ccgc718/p/18709078

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ccgc718  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起