组合数取模
为方便,记$C(n,m)=C_n^m=\binom{n}{m}$。
struct Factorial : Mod
{
vector<ll> fac, ifac;
Factorial(int N, ll M) : fac(N, 1), ifac(N, 1), Mod(M)
{
for (int i = 2; i < N; ++i)
fac[i] = mul(fac[i - 1], i), ifac[i] = mul(M - M / i, ifac[M % i]);
for (int i = 2; i < N; ++i)
ifac[i] = mul(ifac[i], ifac[i - 1]);
}
ll c(int n, int m) { return mul(mul(fac[n], ifac[m]), ifac[n - m]); }
ll lucas(ll n, ll m) //卢卡斯定理求C(n,m)%M,适用于模数M小于N的情况,或者m较小的时候也可以暴力求
{
if (!m)
return 1;
if (n < m || n % M < m % M)
return 0;
if (n < M && m < M)
return c(n, m);
return mul(lucas(n / M, m / M), lucas(n % M, m % M));
}
};
组合数LCM
$(n + 1)lcm(C(n,0),C(n,1),\ldots,C(n,k))=lcm(n+1,n,n−1,n−2,\ldots,n−k+1)$
区间lcm的维护:对于一个数,将其分解质因数,若有因子$p^k$,那么拆分出k个数 $p^1,p^2,\ldots,p^k$,权值都为p,那么区间$[l,r]$内所有数的lcm的答案=所有在该区间中出现过的数的权值之积,可持久化线段树维护之。
Stirling数
第一类斯特林数
第一类斯特林数$S(p,k)$的一个的组合学解释是:将p个物体排成k个非空循环排列的方法数。
递推公式:$S(p,k)=(p−1)S(p−1,k)+S(p−1,k−1),1\leq k\leq p−1;S(p,0)=0,p\ge1;S(p,p)=1,p\ge0$
第二类斯特林数
第二类斯特林数$S(p,k)$的一个的组合学解释是:将p个物体划分成k个非空不可辨别的(可以理解为盒子没有编号)集合的方法数。
递推公式:$S(p,k)=kS(p−1,k)+S(p−1,k−1),1\leq k\leq p−1;S(p,0)=0,p\ge 1;S(p,p)=1,p\ge0$
卷积形式:$S(n,m)=\frac{1}{m!}\sum_{k=0}^m(-1)^kC(m,k)(m-k)^n=\sum_{k=0}^m\frac{(-1)^k}{k!}\frac{(m-k)^n}{(m-k)!}$
同时有转化:$x^k=\sum_{i=1}^ki!C(x,i)S(k,i)$
斯特林近似公式
$n!\approx\sqrt{2\pi n}(\frac{n}{e})^n$
小球入盒模型通解
k个球 | m个盒子 | 空盒子 | 方案数 |
---|---|---|---|
各不相同 | 各不相同 | 允许 | $m^k$ |
各不相同 | 各不相同 | 无 | $m!Stirling2(k,m)$ |
各不相同 | 完全相同 | 允许 | $\sum_{i=1}^mStirling2(k,i)$ |
各不相同 | 完全相同 | 无 | $Stirling2(k,m)$ |
完全相同 | 各不相同 | 允许 | $C(m+k−1,k)$ |
完全相同 | 各不相同 | 无 | $C(k−1,m−1)$ |
完全相同 | 完全相同 | 允许 | $\frac{1}{(1−x)(1−x^2)…(1−x^m)}的x^k项的系数$ |
完全相同 | 完全相同 | 无 | $\frac{x^m}{(1−x)(1−x^2)…(1−x^m)}的x^k项的系数$ |
置换
struct Permutation : vector<int>
{
Permutation(int n = 0) : vector<int>(n) {}
friend Permutation operator*(const Permutation &f, const Permutation &g)
{
Permutation ans(f.size());
for (int i = 0; i < f.size(); ++i)
ans[i] = g[f[i]];
return ans;
}
friend Permutation inv(const Permutation &f)
{
Permutation ans(f.size());
for (int i = 0; i < f.size(); ++i)
ans[f[i]] = i;
return ans;
}
friend vector<vector<int>> cycle(const Permutation &f)
{
vector<int> vis(f.size(), 0);
vector<vector<int>> ans;
for (int i = 0; i < f.size(); ++i)
if (!vis[i])
{
ans.push_back(vector<int>());
for (int j = i; !vis[j]; j = f[j])
vis[j] = 1, ans.back().push_back(j);
}
return ans;
}
};
生成字典序
下一排列
对给定的排列$a_1a_2\ldots a_n$,找到$a_j$使得$a_j<a_{j+1},a_{j+1}>a_{j+2}>\ldots>a_n$即这列数中最后一个相邻递增数对,然后把$a_{j+1},a_{j+2},\ldots,a_n$中大于$a_j$的最小数放到位置j,然后$a_j\ldots a_n$中剩余的数从小到大排序放到$[j+1,n]$中。
bool nextPermutation(ll *b, ll *e) //标准库有这个函数next_permutation
{
ll *i = e - 1, *j = e - 2;
while (j >= b && *j >= *(j + 1))
--j;
if (j < b)
return 0;
while (*i <= *j)
--i;
return swap(*i, *j), reverse(j + 1, e), 1;
}
二项式反演
$f(n)=\sum_{k=0}^nC(n,k)g(k),g(n)=\sum_{k=0}^n(−1)^{n−k}C(n,k)f(k)$
第k小期望
$f(n,k)$表示有n个变量,和为1,第k小的期望。
$f(n,k)=\frac{1}{n^2}+(1-\frac{1}{n})f(n-1,k-1),f(n,0)=0$
错排数
考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。
n个元素的错排数$D_n$满足递推公式:$D_1=0,D_2=1,D_n=(n−1)(D_{n−2}+D_{n−1})$
通项:$D(n)=n![\frac{(-1)^2}{2!}+\ldots+\frac{(-1)^{n-1} }{(n-1)!}+\frac{(-1)^n}{n!}]=\lfloor\frac{n!}{e}+\frac{1}{2}\rfloor$
Bonuli数
$B_n = -\frac{1}{C(n+1,n)}(C(n+1,0)B_0+C(n+1,1)B_1+\ldots+C(n+1,n-1)B_{n-1})=-\frac{1}{n+1}(C(n+1,0)B_0+C(n+1,1)B_1+\ldots+C(n+1,n-1)B_{n-1})$
可用于计算任意正整数次数的幂和:$\sum_{i=1}^ni^k=\frac{1}{k+1}\sum_{j=0}^kC(k+1,j)B_jn^{k+1-j}$
struct Bonuli : Factorial
{
vector<ll> b;
Bonuli(int N, ll M) : Factorial(N, M), b(N, 0)
{
for (int i = b[0] = 1; i < N; ++i)
{
for (int j = 0; j < i; ++j)
b[i] = qadd(b[i], mul(b[j], c(i + 1, j)));
b[i] = qadd(M, -mul(b[i], mul(fac[i], ifac[i + 1])));
}
}
ll ask(ll n, int k)
{
ll r = 0, w = 1, u = add(n, 1);
for (int i = 1; i <= k + 1; ++i)
r = qadd(r, mul(mul(b[k + 1 - i], c(k + 1, i)), w = mul(w, u)));
return mul(r, mul(fac[k], ifac[k + 1]));
}
} b(1 << 11, 1e9 + 7);
Catalan数
$h_1=1,h_n=\frac{4n−2}{n+1}h_{n−1}=\frac{C(2n,n)}{n+1}=C(2n,n)−C(2n,n−1)$。
在一个格点阵列中,从$(0,0)$点走到$(n,m)$点且不经过对角线$x=y$的方法数:$C(n+m−1,m)−C(n+m−1,m−1),x>y;C(n+m,m)−C(n+m,m−1),x\ge y$。
常见的Catalan数:括号序的个数、凸多边形三角剖分的方案数等。
Bell数
把n个带标号的物品划分为若干不相交集合的方案数称为贝尔数,其递推公式:$B_n=\sum_{i=0}^{N-1}C_{n-1}^iB_i$
前几项贝尔数:
1,2,5,15,52,203,877,4140,21147,115975,678570,4213597,27644437,190899322,1382958545,...
下为$O(P^2\log P)$求$B_n$对P取模。
struct Bell
{
static const int P = 999999598, N = 7284;
int a[4], f[N], s[2][N], i, j, x;
Bell()
{
a[0] = 2, a[1] = 13, a[2] = 5281, a[3] = 7283; //P的质因数分解
f[0] = f[1] = s[0][0] = 1, s[0][1] = 2;
for (i = 2, x = 1; i < N; i++, x ^= 1)
for (f[i] = s[x][0] = s[x ^ 1][i - 1], j = 1; j <= i; ++j)
s[x][j] = (s[x ^ 1][j - 1] + s[x][j - 1]) % P;
}
int cal(int x, ll n)
{
int i, j, k, m = 0, b[N], c[N], d[70];
for (i = 0; i <= x; i++)
b[i] = f[i] % x;
while (n)
d[m++] = n % x, n /= x;
for (i = 1; i < m; i++)
for (j = 1; j <= d[i]; j++)
{
for (k = 0; k < x; k++)
c[k] = (b[k] * i + b[k + 1]) % x;
c[x] = (c[0] + c[1]) % x;
for (k = 0; k <= x; k++)
b[k] = c[k];
}
return c[d[0]];
}
ll ask(ll n)
{
if (n < N)
return f[n];
ll t = 0;
for (int i = 0; i < 4; ++i)
t = (t + (P / a[i]) * pow(P / a[i], a[i] - 2, a[i]) % P * cal(a[i], n) % P) % P;
return t;
}
};
等价类容斥
考虑容斥,Bell(p)枚举所有等价情况。对于一种情况,强制了一个等价类里面的数都要相同,其它的可以相同也可以不同。
容斥系数为:$(−1)^{p−等价类个数}(每个等价类大小−1)!之积$。
Grey码
格雷序列第i个是i^(i>>1)
。长为n的01序列共$2^n$个,下标从$0\ldots 2^n-1$。
扩展Cayley公式
对于n个点,m个连通块的图,假设每个连通块有a[i]个点,那么用s−1条边把它连通的方案数为$n^{s−2}a[1]a[2]\ldots a[m]$。
超立方体
n维超立方体有$2^{n−i}C(n,i)$个i维元素。
枚举位集I的非空子集J
for(J=I; J; J=I&J−1) {}
![]() |
![]() |