这个组合数的问题还是很常见的,就是问 C n m C_n^m Cnm,然后会根据询问的次数,以及n和m的大小来判断怎么去做。本文用了一写基本的数论公式来进行推导,包括卢卡斯定理等,进行计算。
C n m = n ⋅ ( n − 1 ) ⋅ ( n − 2 ) ⋯ ( n − m + 1 ) m ⋅ ( m − 1 ) ⋅ ( m − 2 ) ⋯ 1 = n ! m ! ( n − m ) ! C_n^m=\frac{n\cdot(n-1)\cdot(n-2)\cdots(n-m+1)}{m\cdot (m-1)\cdot (m-2)\cdots 1}=\frac{n!}{m!(n-m)!} Cnm=m⋅(m−1)⋅(m−2)⋯1n⋅(n−1)⋅(n−2)⋯(n−m+1)=m!(n−m)!n!
实际意义:从n件物品中,取m件,总共有多少种取法(不区分取的先后顺序)
C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1
理解:把这n份物品分为1和n-1,那么所有的取法为:取这个1和不取这个1,即公式
题目描述:
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cba mod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码示例:
#include
#include
using namespace std;
const int N = 2010, mod = 1e9+7;
int c[N][N];
void init() // 预处理
{
for(int i = 0; i < N; ++i)
{
for(int j = 0; j <= i; ++j)
{
if(!j) c[i][j] = 1; // 当j为0 c[i][j]必为1
else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod; // 公式
}
}
}
int main()
{
int n;
scanf("%d", &n);
init();
while(n--)
{
int n, m;
scanf("%d%d", &n, &m);
printf("%d\n", c[n][m]);
}
return 0;
}
C n m = n ! m ! ( n − m ) ! C_n^m=\frac{n!}{m!(n-m)!} Cnm=m!(n−m)!n! 由于除法不方便则用逆元代替: C n m ≡ n ! ⋅ ( m ! ) − 1 ( n − m ) ! − 1 ( m o d p ) 由于除法不方便则用逆元代替:C_n^m \equiv n!\ \cdot\ (m!)^{-1}\ (n-m)!^{-1} \pmod p 由于除法不方便则用逆元代替:Cnm≡n! ⋅ (m!)−1 (n−m)!−1(modp)求逆元可以参考我之前写的博客快速幂、逆元
题目描述:
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000 ,1≤b≤a≤105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码示例:
#include
#include
using namespace std;
typedef long long LL;
const int N = 1e5+10, mod = 1e9+7;
int fact[N], infact[N]; //阶乘 阶乘的逆元
int qmi(int a, int k, int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
void init()
{
fact[0] = infact[0] = 1;
for(int i = 1; i < N; ++i)
{
fact[i] = (LL)fact[i-1] * i % mod;
infact[i] = (LL)infact[i-1] * qmi(i, mod - 2, mod) % mod;
}
}
int main()
{
int n;
scanf("%d", &n);
init();
while(n--)
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a-b] % mod);
}
return 0;
}
卢卡斯定理: C n m ≡ C n m o d p m m o d p ⋅ C n / p m / p ( m o d p ) 卢卡斯定理:C_n^m\ \equiv\ {C_{n\ mod\ p}^{m\ mod\ p}\ \cdot\ C_{n/p}^{m/p}} \pmod{p} 卢卡斯定理:Cnm ≡ Cn mod pm mod p ⋅ Cn/pm/p(modp) C n m ≡ n ! ⋅ ( m ! ) − 1 ( n − m ) ! − 1 ( m o d p ) C_n^m \equiv n!\ \cdot\ (m!)^{-1}\ (n-m)!^{-1} \pmod p Cnm≡n! ⋅ (m!)−1 (n−m)!−1(modp)
题目描述:
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 Cbamodp 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤20,1≤b≤a≤1018 ,1≤p≤105,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
代码示例:
#include
#include
using namespace std;
typedef long long LL;
int qmi(int a, int k, int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b, int p)
{
if(b > a) return 0;
int res = 1;
for(int i = a, j = b; j >= 1; i--, j--)
{
res = (LL)res * i % p;
res = (LL)res * qmi(j, p-2, p) % p;
}
return res;
}
int Lucas(LL a, LL b, int p)
{
if(a < p && b < p) return C(a,b,p);
else return (LL)Lucas(a/p, b/p, p) * C(a%p, b%p, p) % p;
}
int main()
{
int n;
scanf("%d", &n);
while(n--)
{
LL a, b;
int p;
scanf("%lld%lld%d", &a, &b, &p);
printf("%d\n", Lucas(a,b,p));
}
return 0;
}
思想:
把 C a b C_a^b Cab分解质因数,然后用高精度乘法将每次的结果与分解的质因数相乘。
高精度乘法可参考我之前的博客高精度乘法
C a b = a ! b ! ⋅ ( a − b ) ! C_a^b=\frac{a!}{b!\ \cdot\ (a-b)! } Cab=b! ⋅ (a−b)!a! a ! 中含质因子 p 的个数: a p + a p 2 + a p 3 + ⋯ ( 除法皆为下取整 ) a!中含质因子p的个数:\frac{a}{p}+\frac{a}{p^2}+\frac{a}{p^3}+\cdots (除法皆为下取整) a!中含质因子p的个数:pa+p2a+p3a+⋯(除法皆为下取整)
如何分解质因数:
可以先求出来 a ! m o d p a!\ mod\ p a! mod p的质因数的个数,再求出 b ! 和 ( a − b ) ! 的个数,两个相减就可以了 b!和(a-b)!的个数,两个相减就可以了 b!和(a−b)!的个数,两个相减就可以了
题目描述:
输入 a,b,求 Cba 的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,输出 Cba 的值。
数据范围
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10
代码示例:
#include
#include
#include
using namespace std;
const int N = 5010;
int primes[N], cnt;
bool st[N];
int sum[N]; //质因子的个数
void get_primes(int n)
{
for(int i = 2; i <= n; ++i)
{
if(!st[i]) primes[cnt++] = i;
for(int j = 0; primes[j] * i <= n; ++j)
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
vector<int> mul(vector<int>& a, int b)
{
vector<int> res;
int t = 0;
for(int i = 0; i < a.size() || t; ++i)
{
if(i < a.size()) t += a[i] * b;
res.push_back(t % 10);
t /= 10;
}
while(res.size() > 1 && res.back() == 0) res.pop_back();
return res;
}
int get(int n, int p) // 求n!中质因子p的个数
{
int res = 0;
while(n)
{
res += n / p;
n /= p;
}
return res;
}
int main()
{
int a, b;
cin >> a >> b;
get_primes(a);
for(int i = 0; i < cnt; ++i)
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a-b, p); // 求包含质因子primes[i]的个数
}
vector<int> res;
res.push_back(1);
for(int i = 0; i < cnt; ++i)
{
for(int j = 0; j < sum[i]; ++j)
{
res = mul(res, primes[i]); // 用高精度与每一个质因子相乘
}
}
for(int i = res.size() - 1; i >= 0; --i) printf("%d", res[i]); // 输出结果
return 0;
}