组合计数
1. 组合计数原理
原理
-
组合计数包含很多内容,比如乘法原理,加法原理,组合数,排列数等。
-
组合计数中存在的方法:
(1)递推法;对应AcWing 1307. 牡牛和牝牛;
(2)隔板法;对应AcWing 1308. 方程的解;
(3)加法原理、乘法原理;
(4)组合数,排列数;
C a b = a ! b ! × ( a − b ) ! = a × ( a − 1 ) × . . . × ( a − b + 1 ) 1 × 2 × . . . × b C_a^b = \frac{a!}{b! \times (a-b)!} = \frac{a \times (a-1) \times ... \times (a-b+1)}{1 \times 2 \times ... \times b} Cab=b!×(a−b)!a!=1×2×...×ba×(a−1)×...×(a−b+1)P a b = a ! ( a − b ) ! = a × ( a − 1 ) × . . . × ( a − b + 1 ) P_a^b = \frac{a!}{(a-b)!} = a \times (a-1) \times ... \times (a-b+1) Pab=(a−b)!a!=a×(a−1)×...×(a−b+1)
(5)
Lucas
定理;(6)
Catalan
数列。
2. AcWing上的组合计数题目
AcWing 1307. 牡牛和牝牛
问题描述
-
问题链接:AcWing 1307. 牡牛和牝牛
分析
-
令牡牛为1,牝牛0,则问题是:任意两个1之间至少有
K
个0。 -
这一题使用到的技巧是递推,类似于DP中的状态转移(
N
头牛的位置下标为1~N
)。
-
另外还需要考虑一些边界情况,即当
i-k-1<0
时,此时f(i) = f(0)
。 -
此时我们还没求出所有的合法方案,因为
f(n)
表示最后一个1在第n
个位置的方案数。所有的合法方案可以分为若干类,分类标准是最后一个1所在的位置,则没有1对应f(0)
,最后一个1在第一个位置对应f(1)
,…,因此将所有的f(i)
加起来就是最后的结果。 -
求数组
f
可以使用前缀和求解。
代码
- C++
#include <iostream>
using namespace std;
const int N = 1000010, mod = 5000011;
int n, k;
int f[N]; // 长度为i且以1结尾的字符串的数量
int s[N]; // f的前缀和
int main() {
cin >> n >> k;
f[0] = s[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = s[max(i - k - 1, 0)];
s[i] = (s[i - 1] + f[i]) % mod;
}
cout << s[n] << endl;
return 0;
}
AcWing 1308. 方程的解
问题描述
-
问题链接:AcWing 1308. 方程的解
分析
-
对于 x x m o d 1000 x^x \ mod \ 1000 xx mod 1000,可以使用快速幂求解,假设结果为
n
。 -
剩下的问题变成将
n
分成k
个数,且每个数必须大于0,问有多少种方法?可以使用隔板法,这种方法很常用。 -
相当于一共
n
个小球排成一排,然后在n-1
个空挡中插入k-1
个隔板(每个空挡最多插入一个隔板),有多少种方式? -
答案是组合数: C n − 1 k − 1 C_{n-1}^{k-1} Cn−1k−1,可以参考各种不同数据范围的组合数求解方法,因为这里数据比较小,可以直接使用递推法求解即可。
-
还需要估计一下本题最大的值为多少,最大为:
C 1000 100 = 1000 ! 100 ! × 900 ! C_{1000}^{100} = \frac{1000!}{100! \times 900!} C1000100=100!×900!1000!
- 因为数据过大,因此这里使用数组记录结果,数组长度开到150完全足以。
代码
- C++
#include <iostream>
using namespace std;
const int N = 150; // 每个组合数不会超过150位
int k, x;
int f[1000][100][N]; // f[i][j]表示组合数C(i, j)
int qmi(int a, int k, int p) {
int res = 1 % p;
while (k) {
if (k & 1) res = (res * a) % p;
a = (a * a) % p;
k >>= 1;
}
return res;
}
// c = a + b
void add(int c[], int a[], int b[]) {
for (int i = 0, t = 0; i < N; i++) {
t += a[i] + b[i];
c[i] = t % 10;
t /= 10;
}
}
int main() {
cin >> k >> x;
int n = qmi(x % 1000, x, 1000);
// C(n - 1, k - 1)
for (int i = 0; i < n; i++)
for (int j = 0; j <= i && j < k; j++)
if (!j) f[i][j][0] = 1;
else add(f[i][j], f[i - 1][j], f[i - 1][j - 1]);
// 输出结果, g[0]代表最低位
int *g = f[n - 1][k - 1];
int i = N - 1;
while (!g[i]) i--;
while (i >= 0) cout << g[i--];
return 0;
}
AcWing 1309. 车的放置
问题描述
-
问题链接:AcWing 1309. 车的放置
分析
-
首先我们考虑如果是一个规则的图形,会有多少中放置方法:对于一个 n × m n \times m n×m的长方形来说,需要放置
k
个车(车是无差别的),则对于行来说,我们可以从n
行中选择k
行来放置,即 C n k C_n^k Cnk;然后对于选择的k
行,第一行可以有m
种放置方法,第二行m-1
种,根据乘法原理,对于选择的k
行一共有 P m k P_m^k Pmk种选法。 -
因此对于一个一个 n × m n \times m n×m的长方形来说,放置
k
个车的方案数是 C n k × P m k C_n^k \times P_m^k Cnk×Pmk。 -
对于本题,我们可以将这个不规则的图形分成两个矩形,有两种分割方式,使用任意一种即可,这里将其分割为上下两个矩形,如下图:
- 因为一共要放置
k
个车,我们可以枚举一下上下两个矩形中车的数量,假设上面矩形数量为i
,则方案数为:
∑ i = 0 k C b i × P a i × C d k − i × P a + c − i k − i \sum_{i=0}^k C_b^i \times P_a^i \times C_{d}^{k-i} \times P_{a+c-i}^{k-i} i=0∑kCbi×Pai×Cdk−i×Pa+c−ik−i
-
这里求组合数的方式类似于AcWing 886. 求组合数 II,可以参考组合数的分析。
-
因为
p=100003
是一个质数,所以任何小于p的数都与p互质,因此逆元存在,另外由于p是质数,可以使用快速幂求解逆元。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2010, mod = 100003;
int fact[N]; // 阶乘
int infact[N]; // 对应阶乘的逆元
// 快速幂
int qmi(int a, int k) {
int res = 1 % mod;
while (k) {
if (k & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
k >>= 1;
}
return res;
}
// 返回组合数
int C(int a, int b) {
if (a < b) return 0;
return (LL)fact[a] * infact[a - b] * infact[b] % mod;
}
// 返回排列数
int P(int a, int b) {
if (a < b) return 0;
return (LL)fact[a] * infact[a - b] % mod;
}
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;
}
int a, b, c, d, k;
cin >> a >> b >> c >> d >> k;
int res = 0;
for (int i = 0; i <= k; i++)
res = (res + (LL)C(b, i) * P(a, i) % mod * C(d, k - i) % mod * P(a + c - i, k - i)) % mod;
cout << res << endl;
return 0;
}
AcWing 1310. 数三角形
问题描述
-
问题链接:AcWing 1310. 数三角形
分析
-
对于一个 n × m n \times m n×m的网格,其格点的是 ( n + 1 ) × ( m + 1 ) (n+1) \times (m+1) (n+1)×(m+1)的,这里为了处理方便,读入
n、m
后,直接都进行加一操作,变为格点数量,因此下面的分析中n、m
表示的是格点的数量。 -
这里直接求解有多少种方案很难,因此我们需要曲线救国,先求出所有方案数,然后减去不合法的方案数,就可以得到最终的结果。
-
所有的方案数: C n × m 3 C_{n \times m}^3 Cn×m3。
-
不合法的方案数,所有不合法的方案必定是三点在同一条直线上,如果放在坐标系中来看,左下点为源点,则分为如下几种情况:
(1)斜率为0: n × C m 3 n \times C_m^3 n×Cm3;
(2)斜率为无穷大: m × C n 3 m \times C_n^3 m×Cn3;
(3)斜率大于0:首先这三个点在同一条线段上,假设该线段向
x
轴投影的长度为i
,向y
轴投影的长度为j
,则所有的情况是(i, j)
取遍(2,2)~(n,m)
,如下图:那么在这个会有多少这样的线段呢?我们只需要关注线段左端点有多少种取法即可,可以发现对于
(i, j)
,一共存在 ( m − i ) × ( n − j ) (m - i) \times (n - j) (m−i)×(n−j)种取法;然后还需要考虑一个问题,这个线段中中间的第三个点有多少中取法,也就是说这条线段会经过多少格点?如果不包括两个端点的话,答案是
gcd(i, j)-1
。关于这个问题的解释如下:因此:所有斜率大于0的不合法方案为:
∑ i , j ( m − i ) × ( n − j ) × ( g c d ( i , j ) − 1 ) 2 ≤ i ≤ n , 2 ≤ j ≤ m \sum _{i, j} (m - i) \times (n - j) \times (gcd(i, j) - 1) \quad \quad 2 \le i \le n, 2 \le j \le m i,j∑(m−i)×(n−j)×(gcd(i,j)−1)2≤i≤n,2≤j≤m
(4)斜率小于0:和(3)是对称的,因此和(3)的方案数相同。 -
因此,最终合法的方案数为(其中 2 ≤ i ≤ n , 2 ≤ j ≤ m 2 \le i \le n, 2 \le j \le m 2≤i≤n,2≤j≤m):
C n × m 3 − n × C m 3 − m × C n 3 − 2 × ∑ i , j ( m − i ) × ( n − j ) × ( g c d ( i , j ) − 1 ) C_{n \times m}^3 - n \times C_m^3 - m \times C_n^3 - 2 \times \sum _{i, j} (m - i) \times (n - j) \times (gcd(i, j) - 1) Cn×m3−n×Cm3−m×Cn3−2×i,j∑(m−i)×(n−j)×(gcd(i,j)−1)
- 结果不会超过
1
0
18
10^{18}
1018,因此可以使用
long long
存储结果。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
// 返回C(n, 3)
LL C(int n) {
return (LL)n * (n - 1) * (n - 2) / 6;
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int main() {
int n, m;
cin >> m >> n;
n++, m++;
LL res = C(n * m) - (LL)n * C(m) - (LL)m * C(n);
for (int i = 2; i <= m; i++) // m - i
for (int j = 2; j <= n; j++) // n - j
res -= 2ll * (m - i) * (n - j) * (gcd(i, j) - 1);
cout << res << endl;
return 0;
}
AcWing 1312. 序列统计
问题描述
-
问题链接:AcWing 1312. 序列统计
分析
这里可以枚举序列的长度,假设当前的序列长度为k
,则存在两种分析方式(第二种方式更加简单):
分析方式1
-
使用这种方式分析的目的是为了介绍不等式使用隔板法求解的过程。
-
对于
[L, R]
这个区间中满足条件的数据,我们可以将区间变为[0, R-L]
满足条件的数据,两者是一一对应的,因此,我们只需要考虑在区间[0, R-L]
合法的序列即可,假设合法的序列为:
0 ≤ a 1 ≤ a 2 ≤ . . . ≤ a k ≤ R − L 0 \le a_1 \le a_2 \le ... \le a_k \le R-L 0≤a1≤a2≤...≤ak≤R−L
我们构造a
的差分数组b
,即:
b
1
=
a
1
b
2
=
a
2
−
a
1
.
.
.
b
k
=
a
k
−
a
k
−
1
b_1 = a_1 \\ b_2 = a_2 - a_1 \\ ... \\ b_k = a_k - a_{k-1}
b1=a1b2=a2−a1...bk=ak−ak−1
则问题就变成了:对于任意
b
i
≥
0
b_i \ge 0
bi≥0,满足
b
1
+
b
2
+
.
.
.
+
b
k
≤
R
−
L
b_1 + b_2 + ... + b_k \le R-L
b1+b2+...+bk≤R−L 的方案数。
-
如何求解这个不等式的方案数呢?直接求不是很好求,我们还需要一步等价转换,令 c i = b i + 1 c_i = b_i + 1 ci=bi+1,则问题可以转换为:对于任意的 c i > 0 c_i > 0 ci>0,满足 c 1 + c 2 + . . . + c k ≤ R − L + k c_1 + c_2 + ... + c_k \le R-L+k c1+c2+...+ck≤R−L+k,此时就可以使用隔板法分析求解。
-
相当于一共
R-L+k
个小球排成一排,除了第一个小球前面不能放置隔板,其余的R-L+k
个位置(包括最后一个小球的后面)都可以放置隔板,需要放置k
个隔板,最后一个隔板后的小球不算入,前面的k-1
个隔板会得到k
份,每一份对应一个 c i c_i ci,如下图:
- 因此,因此对于当前
k
来说答案就是:
C R − L + k k C_{R-L+k}^k CR−L+kk
分析方式2
- 我们要求出满足 L ≤ a 1 ≤ a 2 ≤ . . . ≤ a k ≤ R L \le a_1 \le a_2 \le ... \le a_k \le R L≤a1≤a2≤...≤ak≤R 的方案数,我们可以构造 b i = a i + i − 1 b_i = a_i + i - 1 bi=ai+i−1,则问题可以转化为:
L ≤ b 1 < b 2 < . . . < b k ≤ R + k − 1 L \le b_1 < b_2 < ... < b_k \le R + k - 1 L≤b1<b2<...<bk≤R+k−1
的方案数,这就很简单了,相当于在区间[L, R+k-1]
中选取k
个元素的方案数,该区间一共R-L+k
个数,因此对于当前k
来说答案就是:
C
R
−
L
+
k
k
C_{R-L+k}^k
CR−L+kk
-
上述两种分析方法已经结束,之后的分析是从 C R − L + k k C_{R-L+k}^k CR−L+kk 开始分析。
-
因为序列的长度
n
最大为 1 0 9 10^9 109,十亿项加和不可取,我们考虑对结果进行化简,最终的答案为(令 m = R − L m = R - L m=R−L):
∑ k = 1 n C m + k k = C m + 1 1 + C m + 2 2 + . . . + C m + n n = C m + 1 m + C m + 2 m + . . . + C m + n m \sum_{k=1}^n C_{m+k}^k = C_{m+1}^1 + C_{m+2}^2 + ... + C_{m+n}^n \\ = C_{m+1}^m + C_{m+2}^m + ... + C_{m+n}^m k=1∑nCm+kk=Cm+11+Cm+22+...+Cm+nn=Cm+1m+Cm+2m+...+Cm+nm
对上述式子加上一项
C
m
+
1
m
+
1
C_{m+1}^{m+1}
Cm+1m+1,再减去一项
C
m
+
1
m
+
1
C_{m+1}^{m+1}
Cm+1m+1,并根据公式:
C
a
b
=
C
a
−
1
b
−
1
+
C
a
−
1
b
C_a^b = C_{a-1}^{b-1} + C_{a-1}^{b}
Cab=Ca−1b−1+Ca−1b,可知:
C
m
+
1
m
+
C
m
+
2
m
+
.
.
.
+
C
m
+
n
m
=
(
C
m
+
1
m
+
1
+
C
m
+
1
m
)
+
C
m
+
2
m
+
.
.
.
+
C
m
+
n
m
−
C
m
+
1
m
+
1
=
C
m
+
2
m
+
1
+
C
m
+
2
m
+
.
.
.
+
C
m
+
n
m
−
C
m
+
1
m
+
1
=
.
.
.
.
.
.
.
.
.
.
.
.
=
C
m
+
n
+
1
m
+
1
−
1
C_{m+1}^m + C_{m+2}^m + ... + C_{m+n}^m \\ = (C_{m+1}^{m+1} + C_{m+1}^m) + C_{m+2}^m + ... + C_{m+n}^m - C_{m+1}^{m+1} \\ = C_{m+2}^{m+1} + C_{m+2}^m + ... + C_{m+n}^m - C_{m+1}^{m+1} \\ = ............ \\ = C_{m+n+1}^{m+1} - 1
Cm+1m+Cm+2m+...+Cm+nm=(Cm+1m+1+Cm+1m)+Cm+2m+...+Cm+nm−Cm+1m+1=Cm+2m+1+Cm+2m+...+Cm+nm−Cm+1m+1=............=Cm+n+1m+1−1
- 因此本题最终的答案是:
C R − L + n + 1 R − L + 1 − 1 C_{R-L+n+1}^{R-L+1} - 1 CR−L+n+1R−L+1−1
-
我们发现
R、L、k
的范围都很大,求这个组合数需要使用AcWing 887. 求组合数 III中的方法,即使用卢卡斯定理,详细内容请参考:网址。 -
卢卡斯定理(要求式中
a、b
为非负整数,p
为质数):
C a b = C a ( m o d p ) b ( m o d p ) × C a / p b / p ( m o d p ) C_a^b = C_{a \ (mod \ p)} ^ {b \ (mod \ p)} \times C_{a / p} ^ {b / p} \ \ (mod \ p) Cab=Ca (mod p)b (mod p)×Ca/pb/p (mod p)
递归求解即可。
- 本题中
p
=
1
0
6
+
3
p=10^6+3
p=106+3,可以验证是一个质数,可以使用
lucas
定理求解。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int mod = 1000003; // 是一个质数,可以使用快速幂求逆元
int qmi(int a, int k) {
int res = 1 % mod;
while (k) {
if (k & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
k >>= 1;
}
return res;
}
// 根据定义计算C(a, b) % mod
int C(int a, int b) {
if (a < b) return 0;
int down = 1, up = 1; // down: 分母, up: 分子
for (int i = a, j = 1; j <= b; i--, j++) {
up = (LL)up * i % mod;
down = (LL)down * j % mod;
}
return (LL)up * qmi(down, mod - 2) % mod;
}
int Lucas(int a, int b) {
if (a < mod && b < mod) return C(a, b);
return (LL)C(a % mod, b % mod) * Lucas(a / mod, b / mod) % mod;
}
int main() {
int T;
cin >> T;
while (T--) {
int n, l, r;
cin >> n >> l >> r;
cout << (Lucas(r - l + n + 1, r - l + 1) - 1 + mod) % mod << endl;
}
return 0;
}
AcWing 1315. 网格
问题描述
-
问题链接:AcWing 1315. 网格
分析
-
用求卡特兰数的方法分析一下这个题目就可以得到答案,关于卡特兰数的分析:网址。
-
我们需要求出点
(n, m)
关于y = x + 1
对称的点的坐标,假设为(a, b)
,则任何一种不合法的方案都可以转化为到达(a, b)
的路径,如下图:
- 则答案为:
C
m
+
n
n
−
C
m
+
n
a
C_{m+n}^{n} - C_{m+n}^{a}
Cm+nn−Cm+na,问题就转变为了如何求解坐标
(a, b)
。这是高中知识,我们可以列方程求解,根据垂直可以得到一个等式,根据线段中点在对称轴上可以得到另一个等式,可以得到:
{ 1 × b − m a − n = − 1 b + m 2 = a + n 2 + 1 \begin{cases} 1 \times \frac{b - m}{a - n} = -1 \\ \frac{b + m}{2} = \frac{a + n}{2} + 1 \end{cases} {1×a−nb−m=−12b+m=2a+n+1
解方程可得:a = m - 1, b = n + 1
。
- 因此答案为:
C m + n n − C m + n m − 1 C_{m+n}^{n} - C_{m+n}^{m - 1} Cm+nn−Cm+nm−1
- 本题需要使用到高精度求解,如果递推的话计算量为 1000 0 2 = 1 × 1 0 8 10000^2=1 \times 10^8 100002=1×108,再加上高精度计算会超时,因此这里求解阶乘的方式然后带入公式求组合数,类似于AcWing 888. 求组合数 IV。
代码
- C++
#include <iostream>
using namespace std;
const int N = 100010;
int primes[N], cnt;
bool st[N];
int a[N], b[N]; // C(m+n, n)结果存储在a中, C(m+n, m-1)结果存储在b中
// 筛质数
void init(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;
}
}
}
// 返回n中质因数p的个数
int get(int n, int p) {
int s = 0;
while (n) s += n / p, n /= p;
return s;
}
// 高精度乘法
void mul(int r[], int &len, int x) {
int t = 0;
for (int i = 0; i < len; i++) {
t += r[i] * x;
r[i] = t % 10;
t /= 10;
}
while (t) {
r[len++] = t % 10;
t /= 10;
}
}
// 返回组合数C(x, y),结果存储在r中, r[0]是最低位
int C(int x, int y, int r[]) {
int len = 1;
r[0] = 1;
for (int i = 0; i < cnt; i++) {
int p = primes[i];
int s = get(x, p) - get(y, p) - get(x - y, p);
while (s--) mul(r, len, p);
}
return len;
}
// 高精度减法
void sub(int a[], int al, int b[], int bl) {
for (int i = 0, t = 0; i < al; i++) {
a[i] -= t + b[i];
if (a[i] < 0) a[i] += 10, t = 1;
else t = 0;
}
}
int main() {
init(N - 1);
int n, m;
cin >> n >> m;
// 求出C(m+n, n)结果存储在a中, C(m+n, m-1)结果存储在b中
int al = C(n + m, m, a); // al是数据a的长度
int bl = C(n + m, m - 1, b); // bl是数据b的长度
// C(m+n, n) - C(m+n, m-1),结果存储在a中
sub(a, al, b, bl);
int k = al - 1;
while (!a[k]) k--;
while (k >= 0) printf("%d", a[k--]);
return 0;
}
AcWing 1316. 有趣的数列
问题描述
-
问题链接:AcWing 1316. 有趣的数列
分析
-
我们要有这种直觉:一旦发现输入是3,输出是5,很可能就是卡特兰数。关于卡特兰数的讲解可以参考:网址。
-
如何判断某个问题是不是卡特兰数呢?一般由两种方式:
(1)能得到公式: h ( n ) = ∑ i = 1 n h ( i − 1 ) × h ( n − i ) h ( 0 ) = 1 h(n) = \sum _{i=1}^{n} h(i-1) \times h(n-i) \quad \quad h(0)=1 h(n)=∑i=1nh(i−1)×h(n−i)h(0)=1;
(2)能挖掘出如下性质:任意前缀中,某种东西的数量 ≥ \ge ≥ 另一种东西数量。
-
从1到
2n
依次考察每个元素放置的位置,1只能放在第一个位置,2只能放在第二个位置,且任意时刻我们放置的数据中奇数项的个数必须大于等于偶数项的数量。否则,假设我们奇数项放置2个元素,偶数项放置3个元素,则不合法,如下图:
-
我们可以这样对应:在从1到
2n
依次考察每个元素时,如果这个数据放到奇数位置,标为0,否则标为1。则任意前缀中0的个数要大于等于1的个数。 -
卡特兰数为:
C 2 n n n + 1 \frac{C_{2n} ^ n}{n+1} n+1C2nn
- 因为这里的
p
不一定是质数,其他数与p
不一定存在逆元,因此不能使用求逆元的方法。因此这里使用卡特兰数推导的前一步公式:
C 2 n n − C 2 n n − 1 C_{2n}^{n} - C_{2n}^{n-1} C2nn−C2nn−1
组合数等于三个阶乘相乘除,因此我们求出各个阶乘的质因数分解,就能得到组合数的模p
后大小。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2000010;
int n, p; // 这里的p不一定是质数
int primes[N], cnt;
bool st[N];
void init(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[i * primes[j]] = 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;
}
int qmi(int a, int k) {
int res = 1 % p;
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 res = 1;
for (int i = 0; i < cnt; i++) {
int prime = primes[i];
int s = get(a, prime) - get(b, prime) - get(a - b, prime);
res = (LL)res * qmi(prime, s) % p;
}
return res;
}
int main() {
cin >> n >> p;
init(n * 2);
cout << (C(n * 2, n) - C(n * 2, n - 1) + p) % p << endl;
return 0;
}