n!(n的阶乘)

我们在这里介绍一些关于n!的性质。

在计数问题中,经常需要用到n!。有必要了解n!在mod p下的一些性质。下面我们假设p是素数,n!=ape(a无法被p整除),并试图求解e和a mod p(把这个东西算出来可以很好的缩小组合数取模的数据)。e是n!中p因子的个数,因此可以使用下面的式子进行计算:

n/p+n/p2+n/p3+……

这个结论很显然,因为n/d和不超过n的能被d整除的个数相等。由于只需要对于pt<=n的t进行计算,因此复杂度O(logp n)。

接下来计算a mod p。首先计算n!=1*2*……*n的因数中不能被p整除的项的积。假设n=10,p=3则有

n!=1*2*4*5*7*8*10*(3*6*9)

1*2*4*5*7*8*10≡1*2*1*2*1*2*1(mod p)

从这个例子可以看出,不能被p整除的项的积等于(p-1)!(n/p)*(n mod p)!事实上根据威尔逊定理(代码的后面有证明),我们有(p-1)!≡-1(mod p)。因为除了1和p-1之外的项都可以和各自的逆元相乘得到1。

然后再处理一下可以被p整除的项就可以了(拿上面的例子来说就是3、6、9,都除以3之后还剩1、2)。具体的程序还是可以用递归来实现。

代码如下:

1 int fact[MAXN];//fact里面存的是已经处理完毕的阶乘的值
2 int mod_fact(int n,int p,int &e){
3     e=0;
4     if(n==0)return 1;
5     int res=mod(n/p,p,e);
6     e+=n/p;
7     if(n/p%2==1)return res*(p-fact[n%p])%p;//乘res递归处理是为了处理后面p的倍数
8     return res*fact[n%p]%p;
9 }

以下是威尔逊定理的证明:

首先我们需要先了解一下缩系:

若整数A1,A2,...,Am模n分别对应0,1,2,...,n-1中所有m个与n互素的自然数,则称集合{A1,A2,...,Am}为模n的一个缩系。

下面我们来证明下威尔逊定理:

威尔逊定理:当且仅当p为素数时:( p -1 )! ≡ -1 ( mod p)

必要性:如果p不是素数,那么它的正因数一定在1,2,3……(p-1)之中,所以gcd(p, (p-1)!)>1所以p一定是素数。

充分性:

若p是素数,取集合 A={1,2,3,...p -1}; 则A 构成模p乘法的缩系,即任意i∈A ,存在j∈A,使得:( i j ) ≡ 1 ( mod p )那么A中的元素是不是恰好两两配对呢? 不一定,但只需考虑这种情况

x^2 ≡ 1 ( mod p )

解得: x ≡ 1 ( mod p ) 或 x ≡ p-1 (mod p)

其余两两配对;故而( p - 1 )! ≡ 1*( p -1 ) ≡ -1 ( mod p)

时间: 2024-10-30 09:00:35

n!(n的阶乘)的相关文章

算法-计算阶乘n!末尾0的个数

算法逻辑转载自计算阶乘n!末尾0的个数: 问题描述    给定参数n(n为正整数),请计算n的阶乘n!末尾所含有"0"的个数.    例如,5!=120,其末尾所含有的"0"的个数为1:10!= 3628800,其末尾所含有的"0"的个数为2:20!= 2432902008176640000,其末尾所含有的"0"的个数为4. 计算公式    这里先给出其计算公式,后面给出推导过程.    令f(x)表示正整数x末尾所含有的&q

1003 阶乘后面0的数量

1003 阶乘后面0的数量 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 n的阶乘后面有多少个0? 6的阶乘 = 1*2*3*4*5*6 = 720,720后面有1个0. Input 一个数N(1 <= N <= 10^9) Output 输出0的数量 Input示例 5 Output示例 1其实只要循环除五就可以找到规律,其实也可以证明出来. 1 #include <iostream> 2 #include <cstdio> 3 #

高精度运算-阶乘累积求和

# include <stdio.h> # include <math.h> # define N 66 int main(){ int s[N] = {0}, a[N] = {0};// s累加和,a累积求阶乘 int i,j,k,n,digit=1; //digit代表的是数字的位数 scanf("%d",&n); a[0]=1; s[0]=1; if(n==1)// 如果是1,阶乘和就是1,直接输出 printf("%d",s[

关于阶乘

描述:给定两个数n,m,其中m是一个素数. 将n(0<=n<=2^31)的阶乘分解质因数,求其中有多少个m. 注:^为求幂符号. 输入: 第一行是一个整数s(0<s<=100),表示测试数据的组数 随后的s行, 每行有两个整数n,m. 输出: 输出m的个数 样例输入 3 100 5 16 2 1000000000 13 样例输出 24 15 83333329 当n,m体量很小的时候,用这个代码就可以AC: 1 #include <iostream> 2 using na

XDU1019 阶乘因子的个数

题意是让你求一个最小的N, 使得N!有M个0,N的阶乘中2的因子是远多于5的因子的个数, 因此我们统计出5的个数就知道其后面有几个0,对于一个数mid, mid!中5的因子的个数为mid/5 + mid/25 + mid/5^3 + ... 可以发现mid越大mid!中的5的个数越多, 因此我们可以二分答案, 代码如下: #include <cstdio> #include <cstring> #include <algorithm> #include <iost

阶乘的精确值

问题描述:输入不超过1000的正整数,输出n!=1*2*3*...*n的结果 1000!约等于4*10^2567 所以设置数组的最大范围为3000 并采用逆序表示 eg: 123000  因为是逆序 所以输出321 1.java import java.util.Arrays; import java.util.Scanner;/** * 阶乘的精确值 * @author NEU-2015 * */ public class Demo { public static void main(Stri

51nod 1435 位数阶乘

1435 位数阶乘 题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题  收藏  关注 X是一个n位数的正整数 (x=a0a1...an−1) 现在定义 F(x)=∏i=0n−1(ai!)  , 比如F(135)=1!*3!*5!=720. 我们给定一个n位数的整数X(至少有一位数大于1,X中可能有前导0), 然后我们去找一个正整数(s)符合以下条件: 1.这个数尽可能大, 2.这个数中不能含有数字0或1. 3.F(s)=F(x)

4.n的高精度阶乘---优化

题目:对于每组测试数据,在一行中给出一非负整数n(n小于等于100) 样例输入 3 5 10 样例输出 6 120 3628800 超时的代码如下:#include <iostream>#include <cstring>using namespace std; int main(){    int n, count = 0;    int a[100] = {1};    cin >> n;    if(n == 0){        cout << 1;

【51NOD-0】1058 N的阶乘的长度

[算法]数学 [题解]n!的位数相当于ans=log10(n!)上取整,然后就可以拆出来加了. 可以用log10(i)或log(i)/log(10) 阶乘好像有个斯特林公式-- #include<cstdio> #include<cmath> using namespace std; int main() { int n; scanf("%d",&n); long double ans=0; for(int i=1;i<=n;i++)ans+=lo

题目1067:n的阶乘--------long long int

此题不难,关键是20的阶乘是long long int 型的,这点需要注意! AC不用递归的代码: #include<iostream> using namespace std; int main() { int n; long long int res=1; while(cin>>n) { int i=1; res=1;; for (i=1;i<n+1;i++) res=res*i; cout<<res<<endl; } return 0; } AC