【算法笔记5.8小节 -组合数 】关于n!的一个问题 || 组合数的计算

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/xunalove/article/details/88301031

一. 关于n!的一个问题

#include<stdio.h>
/*
计算n!中有多少个质因子p
公式: n!中有(n/p + n/p^2 + n/p^3 + n/p^4......)个质因子p
算法的时间复杂度为O(logn)
*/
int cal(int n, int p)
{
    int ans = 0;
    while(n)
    {
        ans+=n/p;
        n/=p;
    }
    return ans;
}
int main()
{
//可以计算n!的末尾有多少个0,由于末尾0的个数等于n!中5的个数
//因为2的个数肯定是大于5的,2和5组合就形成了10
   printf("%d", cal(10,5));
}
// 2

二. 组合数的计算

#include<stdio.h>
typedef long long int LL;
/*
组合数C(n,m)的计算
递推式 C(n, m) = C(n-1,m) + C(n-1,m-1)
*/
/*方法一:通过定义式直接计算
  范围:  n<=20
*/
LL C(LL n, LL m)
{
    LL ans = 1;
    for(LL i=1; i<=n; i++)
        ans*=i;
    for(LL i=1; i<=m; i++)
        ans/=i;
    for(LL i=1; i<=n-m; i++)
        ans/=i;
    return ans;
}


/*
方法二:通过递推公式计算 O(n*n)
*/
LL C1(LL n, LL m)
{
    if(m==0||m==n) return 1;
    return C1(n-1, m) + C1(n-1, m-1);
}


LL ans[67][67] ={0};
LL C2(LL n, LL m)
{
    if(m==0||m==n)
        return 1;
    if(ans[n][m]!=0) return ans[n][m];
    return ans[n][m] = C2(n-1,m)+C2(n-1, m-1);
}

int n;
void calC()
{
    for(int i=0; i<=n; i++)//初始化边界
        ans[i][0] = ans[i][i]=1;
    for(int i=2; i<=n; i++)
        for(int j=0; j<=i/2; j++)
        {
            ans[i][j] = ans[i-1][j] + ans[i-1][j-1];
            ans[i][i-j] = ans[i][j]; //C(i,i-j) = C(i,j)
        }
}

/*
方法三:通过定义式的变形来计算 O(m)
*/
LL C(LL n, LL m)
{
    LL ans = 1;
    for(LL i=1; i<=m; i++)
        ans = ans*(n-m+i)/i;//注意一定要先乘再除
}

int main()
{
//可以计算n!的末尾有多少个0,由于末尾0的个数等于n!中5的个数
//因为2的个数肯定是大于5的,2和5组合就形成了10
   
}
// 2

#include<stdio.h>
typedef long long int LL;
/*
计算C(n,m) % p
*/
/*
方法一:通过递推公式计算
 m <= n <= 1000,且对p的大小和素性没有额外限制
*/
LL ans[67][67] ={0};
LL C2(LL n, LL m, int p)
{
    if(m==0||m==n)
        return 1;
    if(ans[n][m]!=0) return ans[n][m];
    return ans[n][m] = C2(n-1,m)+C2(n-1, m-1) % p;
}

int n;
void calC()
{
    for(int i=0; i<=n; i++)//初始化边界
        ans[i][0] = ans[i][i]=1;
    for(int i=2; i<=n; i++)
        for(int j=0; j<=i/2; j++)
        {
            ans[i][j] = (ans[i-1][j] + ans[i-1][j-1]) % p ;
            ans[i][i-j] = ans[i][j]; //C(i,i-j) = C(i,j)
        }
}

/*
方法二:根据定义式计算
O(Klogn)
m<=n<=10^6
对p的大小和素性没有额外限制
*/
int prime[maxn];//通过筛法得到的素数表
//遍历不超过n的所有质数
int p[n+1] = {0},k=0;
void IsPrime(int n)
{
    for(int i=2; i<=n; i++)
    {
        if(p[i]==0)
        {
            prime[k++] = i;
            for(int j=i+i; j<=n; j++)
                p[j] = true;
        }
    }
}
int binaryPow(int a, int b, int m)//a^b % m
{
    int ans=1;
    while(b>0)
    {
        if(b%2==1)
            ans =  ans*a%,
        a = a*a%m;
        b/=2;
    }
    return ans;
}
int C(int n, int m, int p)
{
    for(int i=0; prime[i]<=n; i++)
    {
        //计算C(n,m)中prime[i]的指数c, cal(n,k)为n!中含质因子k的个数
        int c = cal(n, prime[i]) - cal(m,prime) - cal(n-m, prime);
        //快速幂计算prime^c%p;
        ans =  ans *binaryPow(prime[i], c, p)%p;
    }
    return ans;
}

/*
方法三:通过定义式的变形来计算
m<=n<<10^18
p<=10^5且为素数
*/



/*
方法四:Lucas定理
*/
int Lucas(int n, int m)
{
    if(m==1)    return 1;
    return C(n%p, m%p) * Lucas(n/p, m/p)%p;
}



猜你喜欢

转载自blog.csdn.net/xunalove/article/details/88301031