直接递推出每个组合数的值进行打表,然后问到就查表。
需要用到递推式: C a b = C a − 1 b − 1 + C a − 1 b C^b_a = C^{b- 1}_{a - 1} + C^{b}_{a - 1} Cab=Ca−1b−1+Ca−1b
这个式子意思是:从 a 中 选 b 个人 = 对于一个人 k :(选中 k 后还需要在剩下的 a - 1 个里面再选 b - 1 个,不选 k 还需要在剩下 a - 1 个人里面选出 b 个)。
从 0 0 0 递推到 n n n,而且每个 n n n 都要从 0 0 0 递推到 n n n,所以是: O ( n 2 ) O(n^2) O(n2)
题目链接:https://www.acwing.com/activity/content/problem/content/955/
#include
#include
#include
using namespace std;
typedef long long ll; // 定义长整型别名为ll
const int N = 2010, mod = 1e9 + 7; // 定义常量N和mod
ll c[N][N]; // 定义二维数组c
int main()
{
// 初始化二维数组c
for(int i = 0; i <= 2000; ++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 - 1] + c[i - 1][j]) % mod; // 计算组合数
}
}
int n;
scanf("%d", &n); // 输入测试用例数量
while (n -- ){
int a, b;
scanf("%d%d", &a, &b); // 输入a和b
printf("%d\n", c[a][b]); // 输出c[a][b]
}
}
此时使用递推法肯定会TLE
,于是我们从定义出发求 C n m = n ! ( n − m ) ! m ! C_n^m = \frac{n!}{(n - m)!m!} Cnm=(n−m)!m!n!,我们需要用快速幂求各阶乘与阶乘在模意义下的逆元(当然前提得是模量得是质数或者二者互质)。
根据费马小定理,可以得到阶乘逆元公式: i n f a c [ i ] = i n f a c [ i − 1 ] ∗ q m i ( i , m o d − 2 , m o d ) infac[i] = infac[i - 1] * qmi(i, mod - 2, mod) infac[i]=infac[i−1]∗qmi(i,mod−2,mod)
就是上一个数的逆元再乘上用快速幂求得的这个数的逆元。
我们预处理了每个数的阶乘与阶乘的逆元,所以复杂度就是它俩相乘: O ( n ⋅ l o g ( m o d ) ) O(n·log(mod)) O(n⋅log(mod))
题目链接:https://www.acwing.com/activity/content/problem/content/956/
#include
#include
#include
using namespace std;
typedef long long ll; // 定义长整型别名为ll
const int mod = 1e9 + 7, N = 1e5 + 5; // 定义常量mod和N
ll fac[N], infac[N]; // 定义一维数组fac和infac
// 快速幂运算函数
int qmi(int a, int b, int m){
int res = 1;
while(b){
if(b & 1 == 1) res = (ll)res * a % m; // 如果b是奇数,则累乘到结果中
a = (ll)a * a % m; // a自身平方
b >>= 1; // b右移一位,相当于除以2
}
return res;
}
// 初始化函数
void init(){
fac[0] = infac[0] =1;
for(int i = 1; i < N; ++i){
fac[i] = fac[i - 1] * i % mod; // 计算阶乘
infac[i] = (ll)infac[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); // 输入a和b
// 输出组合数C(a, b),即a个数中选b个的组合数
printf("%d\n", (ll)fac[a] * infac[b] % mod * infac[a - b] % mod);
}
}
L u c a s 定理: C n m = C n / p m / p ⋅ C n m o d p m m o d p ( m o d p ) Lucas定理:\\C_n^m = C_{n / p}^{m / p}·C_{n\ \ mod\ p}^{m\ mod\ p}\ (mod\ p) Lucas定理:Cnm=Cn/pm/p⋅Cn mod pm mod p (mod p)
这样我们就可以将组合数 C n m o d p m m o d p C_{n\ \ mod\ p}^{m\ mod\ p} Cn mod pm mod p 范围控制在较小的模量内,至于 C n / p m / p C_{n / p}^{m / p} Cn/pm/p,我们可以将其再继续递归使用 L u c a s Lucas Lucas 定理求解。
我们可以预先打表求阶乘以及阶乘逆元,那么复杂度就是 O ( p ⋅ l o g p ) O(p·logp) O(p⋅logp)
也可以直接循环求解不打表,对于询问数较少的可以这样求,各有各的好处。
题目链接:https://www.acwing.com/activity/content/problem/content/957/
#include
#include
#include
using namespace std;
typedef long long ll;
// 快速幂运算函数,计算a的b次方对p取模的结果
ll qmi(ll a, ll b, ll p){
ll res = 1;
while(b){
if(b & 1 == 1) res = res * a % p; // 如果b是奇数,则累乘到结果中
a = a * a % p; // a自身平方
b >>= 1; // b右移一位,相当于除以2
}
return res;
}
// 计算组合数C(a, b)对p取模的结果
ll C(ll a, ll b, ll p){
ll res = 1;
for(int i = 1, j = a; i <= b; ++i, --j){
res = res * j % p; // 计算阶乘部分
res = res * qmi(i, p - 2, p) % p; // 计算阶乘的逆元部分
}
return res;
}
// Lucas定理,用于计算大组合数取模的问题
ll lucas(ll a, ll b, ll p){
if(a < p && b < p) return C(a, b, p); // 如果a和b都小于p,直接计算C(a, b)
else return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; // 否则,递归计算
}
int main()
{
int n;
scanf("%d", &n); // 输入测试用例数量
while (n -- ){
ll a, b, p;
scanf("%lld%lld%lld", &a, &b, &p); // 输入a, b, p
printf("%d\n", lucas(a, b, p)); // 输出lucas(a, b, p)的结果
}
}
参考题解:https://www.acwing.com/solution/content/5244/,这篇解释了C()
函数的由来,懒得打字了。
我们需要需处理出 C n m C_n^m Cnm 中包含的质因子的底数以及指数,然后利用高精度乘法求出值。
线性筛是 O ( n ) O(n) O(n),但是高精度的步骤我实在推不出来啊 我是采购啊啊啊,什么都不会呜呜 >_<
题目链接:https://www.acwing.com/activity/content/problem/content/958/
#include
#include
#include
#include
using namespace std;
const int N = 5010;
int primes[N], cnt; // primes数组存储所有素数,cnt为素数的个数
int sum[N]; // sum数组用于存储结果
bool st[N]; // st数组用于标记是否为素数
// 筛选素数
void get_primes(int n){
for(int i = 2; i <= N; ++i){
if(!st[i]) primes[cnt++] = i; // 如果i是素数,则加入到primes数组中
for(int j = 0; primes[j] <= N / i; ++j){
st[i * primes[j]] = true; // 标记i * primes[j]不是素数
if(i % primes[j] == 0) break; // 如果i能被primes[j]整除,则跳出循环
}
}
}
// 计算a中包含多少个p
int get(int a, int p){
int res = 0;
while(a){
res += a / p; // a中包含多少个p
a /= p; // a除以p
}
return res;
}
// 大整数乘法
vector<int> mul(vector<int> a, int b){
vector<int> c;
int t = 0;
for(int i = 0; i < a.size(); ++i){
t += a[i] * b ;
c.push_back(t % 10); // 取模得到当前位的数字
t /= 10; // 进位
}
while(t){ // 处理最后的进位
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b); // 输入a和b
get_primes(a); // 筛选素数
for(int i = 0; i < cnt; ++i){
int p = primes[i];
sum[i] = get(a, p) - get(a - b, p) - get(b, p); // 计算a, a - b, b中包含多少个p
}
vector<int> res;
res.push_back(1); // 初始化结果为1
for(int i = 0; i < cnt; ++i){ // 枚举每一个质数
for(int j = 0; j < sum[i]; ++j){ // 循环primes[i]的指数次
res = mul(res, primes[i]); // 将结果乘以primes[i]
}
}
for(int i = res.size() - 1; i >= 0; -- i){
printf("%d", res[i]); // 输出结果
}
puts(""); // 输出换行
}
从网格图的左下走到右上,只能往右往上走,而且往上走的步数不能超过往右走的步数,也就是路线不能超过绿线,那么所有的走法就是卡特兰数。
而卡特兰数的通项公式即为: ( Ⅰ ) C a t n = C 2 n n − C 2 n n − 1 , ( Ⅱ ) C a t n = 1 n + 1 C 2 n n , ( Ⅲ ) C a t n = 4 n + 2 n + 2 C a t n − 1 (Ⅰ)Cat_n = C_{2n}^n - C_{2n}^{n - 1},\ (Ⅱ)Cat_n = \frac{1}{n + 1}C_{2n}^n,\ (Ⅲ)Cat_n = \frac{4n + 2}{n + 2}Cat_{n - 1} (Ⅰ)Catn=C2nn−C2nn−1, (Ⅱ)Catn=n+11C2nn, (Ⅲ)Catn=n+24n+2Catn−1
Ⅰ、Ⅱ式的具体证明请看 VCR:卡特兰数
Ⅲ式详细证明可以看博客:递推式
Ⅰ式 C a t n = C 2 n n − C 2 n n − 1 Cat_n = C_{2n}^n - C_{2n}^{n - 1} Catn=C2nn−C2nn−1 思路就是:拿所有路线减去错误路线,那么全部路线好求,错误路线该怎么求呢?
Ⅱ式由Ⅰ式化简可得。
题目链接:https://www.acwing.com/activity/content/problem/content/959/
#include
#include
#include
using namespace std;
typedef long long ll; // 定义长整型别名
const int mod = 1e9 + 7; // 定义模数
// 快速幂算法,计算a的b次方对p取模的结果
int qmi(int a, int b, int p){
int res = 1;
while(b){
if(b & 1 == 1) res = (ll)res * a % mod; // 如果b是奇数,则乘上a
a = (ll)a * a % mod; // a自身平方
b >>= 1; // b右移一位,相当于除以2
}
return res;
}
int main()
{
int n;
scanf("%d", &n); // 输入一个整数n
int a = 2 * n, b = n;
int res = 1;
// 计算分子部分,即(a*(a-1)*...*(a-b+1))对mod取模的结果
for(int i = a; i > a - b; --i) res = (ll)res * i % mod;
// 计算分母部分,即(b*(b-1)*...*1)对mod取模的结果,并求其逆元
for(int i = 1; i <= b; ++i) res = (ll)res * qmi(i, mod - 2, mod) % mod;
// 最后再乘上(n+1)的逆元
res = (ll) res * qmi(n + 1, mod - 2, mod) % mod;
cout << res << endl; // 输出结果
}