51nod 2989 组合数

题目链接:http://class.51nod.com/Challenge/Problem.html#problemId=2989

一、题目描述

组合数C(m,n),表示从M个数中选择N个,有多少种不同的方法。

组合数的计算公式如下:

给出m,n输出C(m,n)最后9位数,去掉前缀的0。

输入

一行,两个正整数N和M(N<=1000000,M<=1000000)。

 

输出

一行,输出C(m,n)最后9位数,去掉前缀的0。

 

样例输入

6 3

样例输出

20

二、思路描述

求1个数的最后9位且不考虑前缀0,相当于求这个数%1000000000的结果。

可以将1-n​的每一个数都分解为素数幂的乘积,再统计每个素数的幂之和。

例如:

6!=1×2×3×4×5×6 = 2×3×22×5×2×3 = 24×32×5​

3!=2×3

我们变成这个样子后就可以进行除法(幂为减法):

同底数幂相除,底数不变,指数相减:(同样是以2为底数的)
a÷ a= a(m-n)
例如:a÷ a= a(5-2) = a3

并不需要逐个分解1-n​的每一个数,只需要求解出小于n的所有质数,然后套用求解阶乘后面0的方法(https://www.cnblogs.com/elisa02/p/12811208.html),求每个质数的幂次。

有了每个质因子的幂次后,再套用快速幂来求解(https://www.cnblogs.com/elisa02/p/12793231.html),这样复杂度降为O(nloglogn)。可以处理1e6范围的数据了。

三、代码

#include<cstdio>
#include<iostream>
using namespace std;
long long mod = 1000000000;

long long count(long long n, long long p){//在1~n的范围内含有p质因子的幂次之和是多少  
    long long ans = 0;
    for(long long i = p;i <= n;i *= p){
        ans += n/i;
    }
    return ans;
}

long long p_mod(long long a, long long b, long long m){//a的b次方%m 
    long long ans = 1;
    a = a % m;
    while(b > 0){
        if(b & 1){
            ans = (ans * a) % m;
        }
        a = (a*a) % m;//每一个位置都要乘一次平方 
        b >>= 1;//判断下一个位置了 
    }
    return ans;//返回累积的结果 
}

int main(){
    long long m, n, ans=1, cnt;
    bool primes[1000010]={0};
    cin >> m >> n;
    if(m < n){
        cout << '0' << endl;
        return 0;
    }
    for(int i = 2;i <= m;i++){
        if(!primes[i]){//如果primes[i]不为质数 
            cnt = count(m, i) - count(n, i) - count(m-n, i);
            ans = (ans * p_mod(i, cnt, mod)) % mod;
            for(long long j = (long long)i*i;j <= m;j += i){//如果primes[i]不为质数
                //说明他的倍数不是质数  
                primes[j] = true;
            }
        }
    }
    cout << ans << endl;
    return 0;
}
posted @ 2020-05-10 10:59  elisa02  阅读(325)  评论(0编辑  收藏  举报