文章目录
- 一、求组合数(1≤n≤10000,1≤b≤a≤2000且取模)
- 1.题目描述
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 2.算法
- 二、求组合数(1≤n≤10000,1≤b≤a≤10^5^且取模)
- 1.题目描述
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 2.算法
- 三、求组合数(1≤n≤20,1≤b≤a≤10^18^且取模)
- 1.题目描述
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 2.算法
- 四、求组合数(不取模,高精度)
- 1.题目描述
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 2.算法
- 五、满足条件的01序列
- 1.题目描述
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 2.算法
从a个不同元素中,任取b(b≤a)个元素并成一组,叫做从a个不同元素中取出b个元素的一个组合;从a个不同元素中取出b(b≤a)个元素的所有组合的个数,叫做从a个不同元素中取出b个元素的组合数。因为数据范围不同,编程实现求组合数的方法也有不同。该篇博客将介绍数据范围不同时,分别需要何种求法,并在最后附加一道转化二维图思想的例题。
一、求组合数(1≤n≤10000,1≤b≤a≤2000且取模)
1.题目描述
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cab 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
2.算法
- 公式:Cab = Ca-1b + Ca-1b-1
- 用该公式预处理出所有的值
- 时间复杂度:O(N2)
#include <iostream>
#include <algorithm>
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;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
int main()
{
int n;
init();
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", c[a][b]);
}
return 0;
}
二、求组合数(1≤n≤10000,1≤b≤a≤105且取模)
1.题目描述
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cab mod (109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
2.算法
- Cab = a!/( ( b - a )! * b! )
- 预处理阶乘,用快速幂和费马小定理(如若不知,可在《数论 - 欧拉函数、快速幂、扩展欧几里得算法》中查看)
- 时间复杂度:O(NlogN)
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, 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;
}
int main()
{
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 n;
scanf("%d", &n);
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;
}
三、求组合数(1≤n≤20,1≤b≤a≤1018且取模)
1.题目描述
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 Cab mod p 的值。
输入格式
第一行包含整数 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
2.算法
- 卢卡斯定理: Cab ≡ Camodpbmodp * Ca/pb/p (mod p)
- 组合数依旧用上一题的方法,用快速幂和费马小定理(如若不知,可在《数论 - 欧拉函数、快速幂、扩展欧几里得算法》中查看),不过不需要预处理,而是直接求
- 时间复杂度:O(logpN*p*logp)
#include <iostream>
#include <algorithm>
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 = 1, j = a; i <= b; i ++, j -- )
{
res = (LL)res * j % p;
res = (LL)res * qmi(i, p - 2, p) % p;
}
return res;
}
int lucas(LL a, LL b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
LL a, b;
int p;
cin >> a >> b >> p;
cout << lucas(a, b, p) << endl;
}
return 0;
}
四、求组合数(不取模,高精度)
1.题目描述
输入 a,b,求 Cab 的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,输出 Cab 的值。
数据范围
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10
2.算法
- 首先需要分解质因子(如若不知,可在《数论 - 质数和约数》中查看),根据Cab = a!/( ( b - a )! * b! ),分别求分子分母质因子的次数再相减
- 这样就把式子化为了单纯的乘法,就只需要一个高精度乘法模板
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 5010;
int primes[N], cnt;
int sum[N];
bool st[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] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
//求次数
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= 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;
cin >> 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);
}
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]);
puts("");
return 0;
}
五、满足条件的01序列
1.题目描述
给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。
输出的答案对 109 + 7 取模。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示答案。
数据范围
1≤n≤105
输入样例:
3
输出样例:
5
2.算法
-
我们可以将题目转化以下,转化为一张二维方格图,从(0,0)开始,0为向右走一格,1为向上走一格
-
我们以从(0,0)到(6,6)举例
-
则绿线即为行走过程能触碰的边界,红线则为行走过程中不能触碰且不能超过其上的边界,那么问题转化为从(0,0)到(6,6)不经过红线的路线有多少条
-
从(0,0)到(6,6)共C126条路线,减去其中经过红线的路线即可
-
经过红线路线求发:我们可以任意画经过红线的路线,再第一个相交点关于红线对称,可以发现终点必然在(5,7),它是等价于从(0,0)到(5,7)路线的总数的
-
所以答案也就是:C126 - C125
-
那么化为从(0,0)到(2n,2n)就是C2nn - C2nn-1,则可以运用卡特兰数
-
卡特兰数公式:C2nn - C2nn-1 = C2nn / (n + 1)
-
组合数求法和上例2差不多,都是直接计算阶乘,用快速幂和费马小定理
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
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 main()
{
int n;
cin >> n;
int a = n * 2, b = n;
int res = 1;
for (int i = a; i > a - b; i -- ) res = (LL)res * i % mod;
for (int i = 1; i <= b; i ++ ) res = (LL)res * qmi(i, mod - 2, mod) % mod;
res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;
cout << res << endl;
return 0;
}