N 的阶乘的另一种解法

任何一个数都可以分解成素数乘积,利用着这性质就可以把 N! 保存在一个数组 arg[i] 里面,arg[i] 保存的是 i 的幂。素数可以直接线性打表, 主要的问题就是要求出 arg[i] 数组:

首先要知道 s1 = N/b 表示小于等于 N 的数中有 s1 个能被 b 整除。

s2 = N / (b^2) 表示小于等于 N 的数中有 s2 个能被 b^2 整除(被 b 整除 而且被 b^2 整除)

s3 = N / (b^3) 表示小于等于 N 的数中有 s3 个能被 b^3 整除(被 b 整除 而且被 b^2 整除 而且被 b^3整除)

………

sn = N / (b^n)(被 b 整除而且被 b^2 整除而且被 b^3 整除 … 而且被 b^n 整除)

现在想想 s1 + s2 + s3 + … + sn 表示什么呢?

是不是就是 1 * 2 * 3 * … * N 这个数分解质因数后 b 的个数?

看得出 s2 中的数在 s1 中加了一次(相当于加了2次)

s3 中的数在 s2 和 s1 中都加了一次(相当于加了3次)

sn 中的数载 sn-1 … s2 和 s1 中都加了一次(相当于加了n次)

看懂上面的话就回归正题吧

看看计算 20! 的过程:

1.先求出20以内的素数(2, 3, 5, 7, 11, 13, 17, 19);

2.再求各个素数的阶数

arg[2] = 20/(2^1) + 20/(2^2) + 20/(2^3) + 20/(2^4) = 18;

arg[3] = 20/(3^1) + 20/(3^2) = 8;

arg[5] = 20/(5^1) = 4;

arg[19] = 20/(19^1) = 1;

所以 20! = (2^18) * (3^8) * (5^4) * … * (19^1);

解释:

2、4、6、8、10、12、14、16、18、20能被2整除

4、8、12、16、20能被4整除(即被2除一次后还能被2整除)

8、16能被8整除(即被2除两次后还能被2整除)

这样就得到了2的幂。其它可以依次递推

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

typedef long long ll;

int arg[1000005];
bool isprime[1000005];
ll prime[200005], top;

void Prime(int n) //素数打表
{
    top = 0;
    memset(isprime, true, sizeof(isprime));
    for(int i = 2; i < n; i++){
        if(isprime[i])
            prime[top++] = i;
        for(int k = 0; k < top && i * prime[k] < n; k++){
            isprime[i*prime[k]] = false;
            if(i % prime[k] == 0)
                break;
        }
    }
}

ll Pow(ll x, ll y, ll mod) //快速幂
{
    ll res = 1;
    while(y){
          if (y & 1)
             res = (res * x) % mod;
          y = y >> 1 ;
          x = (x * x) % mod;
    }
    return res;
}

void fac(int *arg, ll n) //计算阶乘
{
    ll len = -1;
    while(prime[++len] <= n){
        ll sum = 0;
        arg[prime[len]] = 0;  //初始化
        ll num = prime[len]; // N/b
        while(num <= n){
            sum += n / num;
            num = num * prime[len]; // b^n
        }
        arg[prime[len]] += sum;
    }
}

int main()
{
    ll n, mod;
    Prime(1000000); //素数打表
    while(cin>>n>>mod){
        fac(arg, n);  //计算阶乘保存在 arg 里面
        ll res = 1;
        for(int i  = 0; prime[i] <= n; i++){
            res = (res * Pow(prime[i], arg[prime[i]], mod)) % mod;
        }
        cout<<res%mod<<endl;
    }
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 07:35:49

N 的阶乘的另一种解法的相关文章

]Leetcode]-[Reorder List ]-三种解法

Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do this in-place without altering the nodes' values. For example,Given {1,2,3,4}, reorder it to {1,4,2,3}. 题目的意思就是,给定一个链表,从两头开始链接, 比如1-2-3-4-5-6,最开始取两头,组成1-

hdu 4521 小明系列问题——小明序列 (间隔至少为d的LIS 两种解法)

先附上资源地址:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html 进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握. 最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂. 1. 计算机的核心是CPU,它承担了所有的计算任务.它就像一座工厂,时刻在运行. 2. 假定工厂的电力有限,一次只能供给一个车间使用.也就是说,一个车间开工的时候,其他车间都必须停工

UVALive 6257 Chemist&#39;s vows --一道题的三种解法(模拟,DFS,DP)

题意:给一个元素周期表的元素符号(114种),再给一个串,问这个串能否有这些元素符号组成(全为小写). 解法1:动态规划 定义:dp[i]表示到 i 这个字符为止,能否有元素周期表里的符号构成. 则有转移方程:dp[i] = (dp[i-1]&&f(i-1,1)) || (dp[i-2]&&f(i-2,2))     f(i,k):表示从i开始填入k个字符,这k个字符在不在元素周期表中.  dp[0] = 1 代码: //109ms 0KB #include <ios

正整数划分的另一种解法 (纯递归)

Step 1: n ==1 : return 1 n == 2 : return  [1,1],[2] Step 2:for n > 2a.arr.push(n)b.arr.push([n-1,1]) c.1 get result of recursion(n-2)c.2 combine n==2 & result => retc.3 remove duplicate record in ret code : var splitN = function f(n){ if(n == 1)

*HDU 1394 经典逆序数的四种解法

1.暴力 [代码]: 1 /*HDU1394暴力写法*/ 2 #include <iostream> 3 #include <string.h> 4 #include <stdio.h> 5 6 using namespace std; 7 8 int A[50005]; 9 int Low[50005],Up[50005]; 10 int main(){ 11 int n; 12 while(~scanf("%d",&n)){ 13 int

最长单调递增子序列的三种解法

问题描述: 找出由n个数组成的序列的最长单调递增子序列 解法一:转化成LCS问题求解,时间复杂度为O(n*n). 思路:原序列为A,把A按升序排序得到序列B,求出A,B序列的最长公共子序列,即为A的最长单调递增子序列. #include<iostream> #include<algorithm> #include<string> #include<cstdio> using namespace std; //转化成LCS问题,时间复杂度O(n*n) int

Gas Station [leetcode] 的两种解法

由于gas总量大于cost总量时,一定可以绕所有城市一圈. 第一种解法: 假设一开始有足够的油,从位置i出发,到位置k时剩余的油量为L(i,k). 对任意的k,L(i,k)根据i的不同,只相差常数. 我们只需要找到最小的L(0, k)对应的k,k+1为所求. 代码如下: int canCompleteCircuit(vector<int> &gas, vector<int> &cost) { int start = 0; int curGas = 0, minGas

POJ 1515 Street Directions --一道连通题的双连通和强连通两种解法

题意:将一个无向图中的双向边改成单向边使图强连通,问最多能改多少条边,输出改造后的图. 分析: 1.双连通做法: 双连通图转强连通图的算法:对双连通图进行dfs,在搜索的过程中就能按照搜索的方向给所有边定向,其中桥不能改造,只能保留双向边. 代码: #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #includ

C语言--求字符串长度的三种解法

问题: 求一个字符串的三种解法 一.计数的方法 #include<stdio.h> #include<assert.h> int my_strlen( char* str) { int count=0; while (*str) { count++; str++; } return count; } int main(void) { char *arr = "abcef"; int ret = my_strlen(arr); printf("%d\n&