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为底数的)
am ÷ an = a(m-n)
例如:a5 ÷ a2 = 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;
}