数论基本概念

1. 整除法

2. 算数基本定理

$p_i$ 为素数,且 $p_i < p_{i + 1}$,则有:

3. 最大公约数与最小公倍数

4. 求一个数的约数个数

一个数的每个因子均由若干个素因子构成,第 $i$ 个素因子共有 $i + 1$ 种取法,所以总个数为($k$ 为素因子的总个数):

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int divisor (int n)//算数基本定理
{
int tot = 1;
for (int i = 2;i * i <= n;++i)
{
if (n % i == 0)
{
int cnt = 0;
while (n % i == 0) n /= i,++cnt;//素因子i 在 num 中的个数
tot *= (cnt + 1);
}
}
if (n > 1) tot *= 2;//剩下的 n 也为素数的情况
return tot;
}

int divisor_direct (int n)//直接暴力枚举
{
int cnt = 0;
for (int i = 1;i * i <= n;++i)
{
if (n % i == 0)
{
++cnt;
if (n / i != i) ++cnt;//完全平方数时会重复计算
}
}
return cnt;
}

素数筛法

1. 朴素筛法

将每一个数的所有倍数都筛去,时间复杂度为:$O(\frac{n}{2} + \frac{n}{3} + \cdots + 1)$。所以当 $\lim(n \to \infty)$ 时,近似为 $O(n\log n)$。

2. 常数优化

枚举小的那个因子。

1
2
3
for (int i = 2;i * i <= n;++i)
if (!flag[i])
for (int j = i * i;j <= n;j += i) flag[j] = 1;

3. 线性筛

时间复杂度为 $O(n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void work (int n)//保证每个数最多被筛一次
{
for (int i = 2;i <= n;++i)
{
if (!flag[i]) p[++cnt] = i;//素数
for (int j = 1;j <= cnt;++j)
{
if (i * p[j] > n) break;//边界
flag[i * p[j]] = 1;//p[j] 是 i * p[j] 的最小素因子
if (i % p[j] == 0) break;//之后 p[j] 不为 i * p[j] 的最小素因子
//若 p[j] = 2,i = 4,则之后 p[j] = 3,i = 4 不符合条件。否则 12 = 2*6 = 3 * 4 被筛的次数 > 1
}
}
}

欧几里得算法

1. 辗转相减

若 $a > b$,则 $\gcd (a,b) = \gcd (a - b,b)$。

2. 辗转相除

若 $a > b$,则 $\gcd (a,b) = \gcd (b,a \bmod b)$。

1
2
3
4
5
int gcd (int a,int b)
{
if (a < b) swap (a,b);
return (!b) ? a : gcd (b,a % b);
}

扩展欧几里得算法

由欧几里得算法可知 $ax + by = \gcd (a,b)$ 一定存在一组解。那么求解 $ax + by = c$ 时,过程如下:

不断递归得到 $\gcd (a,b) \times x + 0 \times y = \gcd (a,b)$,得到特解 $x = 1,y = 0$。

1
2
3
4
5
6
7
8
9
10
11
12
int exgcd (int a,int b,int &x,int &y)
{
int g = a;
if (b)
{
g = exgcd (b,a % b,x,y);
x -= (a / b) * y;
swap (x,y);
}
else x = 1,y = 0;//一组特解
return g;
}

设 $k_1 = \dfrac{a}{\gcd (a,b)},k_2 = \dfrac{b} {\gcd (a,b)}$,显然 $\gcd (k_1,k_2) = 1$。设 $x,y$ 的最小变化单位是 $d_1,d_2$,则此时 $k_1 = d_2,k_2 = d_1$,所以通解是:

欧拉函数

2. 积性函数

设 $p$ 为质数,求 $\phi(p^k)$:

同理,对于两个不同质数的乘积 $n = p_1^{a_1} p_2^{a_2}$,由容斥原理:

因此,欧拉函数是积性函数。

3. 计算通式

将 $n$ 进行质因数分解:

由积性函数性质:

这个公式可以直接用于计算任意正整数的欧拉函数值。

5. 代码

  1. 根据欧拉函数通式求解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void work (int n)
    {
    phi[1] = 1;
    for (int i = 2;i <= n;++i) phi[i] = i;//先标记为自己,方便标记质数,也方便为公式法求解
    for (int i = 2;i <= n;++i)
    {
    if (phi[i] == i)//为素数
    for (int j = i;j <= n;j += i) phi[j] = phi[j] / i * (i - 1);//其中的一个 (pi - 1) / pi
    }
    }
  2. 根据线性筛实现 $O(n)$ 的时间复杂度求解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void work (int n)
    {
    phi[1] = 1;
    for (int i = 2;i <= n;++i)
    {
    if (!flag[i])
    {
    p[++cnt] = i;
    phi[i] = i - 1;//素数 p 的欧拉函数为 p - 1
    }
    for (int j = 1;j <= cnt;++j)
    {
    if (i * p[j] > n) break;
    flag[i * p[j]] = 1;
    if (i % p[j] == 0)
    {
    phi[i * p[j]] = phi[i] * p[j];//若 p 能整除 n / p,则 φ (n) = φ (n / p) * p
    break;
    }
    phi[i * p[j]] = phi[i] * (p[j] - 1);//若 p 与 n / p 互质,且 p 为质数,则 φ (n) = φ (n / p) * (p - 1)
    }
    }
    }
  3. 利用性质 $f$ 的求解。

    1
    2
    3
    4
    5
    6
    void work (int n)
    {
    for (int i = 1;i <= n;++i) phi[i] = i;
    for (int i = 1;i <= n;++i)
    for (int j = i + i;j <= n;j += i) phi[j] -= phi[i];//减去自己所有因子的值即为 φ(i)
    }

欧拉定理

如果 $a,n$ 为正整数,且 $\gcd (a,n) = 1$,则 $a^ {\phi(n)} ≡ 1 \pmod n$。

已知 $a,n \in \rm{N_{+}}$,且 $\gcd (a,n) = 1$。求证 $a^{\phi(n)} \equiv 1 \pmod n$。

乘法逆元

1. 费马小定理

欧拉定理的特殊情况,当 $n$ 为质数时:

2. 计算

$a \div b \% c$ 时,假设存在 $p \times b ≡ 1 \pmod c$,则 $p$ 为 $b$ 的逆元。$a \div b \% c = a \times p \% c$。

相当于 $b,c$ 已知,则可以通过扩展欧几里得算法求逆元,但需要 $\gcd (b,c) = 1$ 时才有解。时间复杂度 $O(n \log n)$。

1
2
3
4
5
6
int inv (int b,int c)
{
int x,y;
exgcd (b,c,x,y);
return (x % c + c) % c;//化成正数
}

当 $c$ 为质数时,逆元可以表示为 $b^{c - 2} \% c$,直接可以使用快速幂求解。

3. 线性求逆元

时间复杂度为 $O(n)$。

1
2
3
4
5
void init (int n,int mod)
{
inv[1] = 1;
for (int i = 2;i <= n;++i) inv[i] = (p - p / i) * inv[p % i] % p;
}

4. 无法求逆元的转化

中国剩余定理

求解以下方程:

构造答案,设

所以直接通过 $n$ 次 $\texttt{exgcd}$ 求出 $s_i$,最后合并即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ll CRT (int n,int a[],int m[])
{
ll M = 1,ans = 0;
for (int i = 1;i <= n;++i) M *= m[i];
for (int i = 1;i <= n;++i)
{
ll x = 0,y = 0;
exgcd (M / m[i],m[i],x,y);
x = (x % m[i] + m[i]) % m[i];
ans += M / m[i] * x * a[i];
ans %= M;
}
return ans;
}

整除分块

求下列式子的值。

若用朴素的算法,时间复杂度为 $O(n)$,显然会 $\texttt{TLE}$。那么再给出一种 $O(\sqrt n)$ 的做法。
先给出所有的 $\lfloor\dfrac{n}{i}\rfloor$ 的取值不会超过 $\sqrt{n}$ 种的证明:

设一段取值均为 $\lfloor\dfrac{n}{i}\rfloor$ 的区间的左、右端点分别为 $L,R$。

代码如下:

1
2
3
4
5
for (int L = 1,R;L <= n;L = R + 1)
{
R = n / (n / L);
ans += 1ll * (R - L * 1) * (n / L);
}

组合数学基础

1. 组合数的两种表示方法

对于递推公式,是对于第 $n$ 项是否选择得出的。

2. 组合数的预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
void init ()
{
c[0][0] = 1;
for (int i = 1;i < n;++i)
{
c[i][i] = c[i][0] = 1;
for (int j = 1;j < i;++j)
{
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
c[i][j] %= MOD;
}
}
}

3. 组合数的变形

其中的$(3)$可以转化为模型。等号左边为班级中的 $n$ 个人选择候选人,然后从候选人中选一个班长与副班长,同一个人可以身兼两职,求最终方案数;等号右边为身兼两职与两个人分别当选的两种情况,有加法原理相加得到,即:

$(4)$ 也一样可以转化为模型。等号右边为班级中有 $n$ 个男生和 $n$ 个女生,从中选 $n$ 人;等号左边相当于等价的,男生选 $i$ 个,女生选 $n - i$ 个,则有

【摘自 CF2077C Binary Subsequence Value Sum题解

  • 证明 $\sum_{i=0}^nC_n^ii=n2^{n-1}$
    $i=C_i^1$,则原式等于 $\sum_{i=0}^nC_n^iC_i^1$,考虑其组合意义,即先从 $n$ 个元素中选若干个,再从若干个元素里选出一个的方案数。那我们先选这一个,有 $n$ 种方案,再从剩下 $n-1$ 个元素中选出若干个,有 $2^{n-1}$ 种方案,所以是 $n2^{n-1}$。

  • 证明 $\sum_{i=0}^nC_n^ii^2=n(n+1)2^{n-2}$
    考虑 $i^2=i+i(i-1)=i+2\times\frac{i(i-1)}{2}=C_i^1+2C_i^2$,则原式等于 $\sum_{i=0}^nC_n^iC_i^1+2C_n^iC_i^2$,等于 $n2^{n-1}+2\sum_{i=0}^nC_n^iC_i^2$,则我们只需解决 $\sum_{i=0}^nC_n^iC_i^2$。
    考虑组合意义,即从 $n$ 个元素里选出若干个,再从若干个元素里选出两个的方案数。那我们先选出这两个,有 $\frac{n(n-1)}{2}$ 种方案,然后再从剩下的元素中选出若干个,有 $2^{n-2}$ 种方案,所以有 $n(n-1)2^{n-3}$ 种方案。

二项式定理

1. 概念

对于每一个 $x + y$ 的选择,则选 $i$ 个 $x$,剩余均选择 $y$ 时,答案为:

因此有:

2. 二项式定理的变形

隔板法

1. 题意

把 $n$ 个相同的小球,放到 $m$ 个不同的盒子中,盒子不能为空,求方案数。或者求 $x_1 + x_2 + \cdots + x_m = n$ 的正整数解。

解法 相当于在 $n - 1$ 个间隔中选择 $m - 1$ 个间隔,将其分为 $m$ 份。故为:

2. 变式

把 $n$ 个相同的小球,放到 $m$ 个不同的盒子中,盒子可以为空,求方案数。或者求 $x_1 + x_2 + \cdots + x_m = n$ 的非负整数解。

解法 相当于在先在每个盒子中放入 $1$ 个球,然后求 $n + m$ 个相同的小球放入 $m$ 个不同的盒子的方案数,盒子不能为空。故为:

错位排列

递推公式为:

对于第 $i$ 项,可以在前 $i - 1$ 个数中选一个数与第 $i$ 个数替换,共 $i - 1$ 中,然后问题变为求 $(i - 1) \times (f_{i - 2})$。也可以将前 $i - 1$ 个数中的一个数放在前 i - 1 个位置中符合条件的某一个位置,相当于求 $(i - 1) \times f_{i - 1}$ 这一新的错排。

圆排列

原来每组排列都有 $n$ 种排列等价,故方案数为:

多重集合的排列

  1. 集合中有 $k$ 种不同类型的对象,每种数量均为无限,从中选 $n$ 个,排列的方案数为:
  1. 集合中有 $k$ 种不同类型的对象,每种数量分别为 $n_1$ 至 $n_k$,从中选 $n$ 个。相当于选出 $n_1$ 个位置放第 $1$ 种,选出 $n_2$ 个位置放第 $2$ 种,以此类推。方案数为:

多重集合的组合

集合中有 $k$ 种不同类型的对象,每种数量均为无限,从中选 $n$ 个。等价于 $k$ 个相同的球放到 $n$ 个不同的盒子中,盒子可以为空。或者等价于选 $k$ 次球,每次都可以选 $n$ 种球,且选择不分先后。组合的方案数为:

康托展开

1. 计算排列的排名

设 $a_i$ 表示 $i$ 位置右边比它小的数的个数,则该排列之前的排列总数为:

倘若 $i$ 位置上的数比该数小,则在它右边的数可以随意放置,若 $i$ 位置上的数等于该数,则继续判断 $i + 1$ 位置的数的情况,以此类推。因此可以由加法原理得可知该排列之前的排列总数。所以求排名的公式为:

2. 计算特定排名的排列

即康托展开的逆展开。

利用康托展开的公式,类似进制转换的方式去确定从高到底的每一位。若排列长度为 $x$,求编号为 $0$ 开始的,编号为 $k$ 的排列。方法如下:$p = \frac{k}{(x - 1)!}$,则说明第一位右边有 $p$ 个数比它小,所以最高位为 $p + 1$,然后取余数重复操作,知道确定排列上的所有数字。

3. 求下一个排列

对于一个排列:$a_1,a_2,\cdots,a_n$。从后往前找到第一个 $p_i < p_{i + 1}$ 的位置,从后往前找到第一个 $p_j < p_i$ 的位置。最后交换 $p_i,p_j$ 并颠倒 $i$ 位置以后的所有元素即可。

斯特林数

1. 第一类斯特林数

求将 $n$ 个不同的物体摆成k个非空环的方案数。采用动态规划的做法,对于第 $i$ 个物体,可以任取一个环加入,也可以构造一个新环,故有 $dp_{n,k} = (n - 1)dp_{n - 1,k} + dp_{n - 1,k - 1}$。(当然有初始化 $dp_{0,0} = dp_{1,1} = 1$)

2.第二类斯特林数

求将 $n$ 个不同的物体分成 $k$ 个非空集合的方案数。采用动态规划的做法,对于第 $i$ 个物体,可以任取一个集合加入,也可以放入一个新集合中,故有 $k \times dp_{n - 1,k} + dp_{n - 1,k - 1}$。(当然有初始化 $dp_{i,i} = dp_{i,1} = 1$)

卡特兰数

题目描述,假如有一个 $n\times n$ 的网络,从左下角 $A$ 点走到右上角 $B$ 点且不穿越对角线的方案数。通过几何方法证明,答案为:

其它的表达形式还有:

常见应用:

  1. 合法的括号匹配
  2. $n$ 个节点二叉树的形态数
  3. 入栈出栈的排列总数
  4. 凸 $n+2$ 边形的分割

放球问题

设 $dp_{i,j}$ 表示 $i$ 个小球放到 $j$ 个盒子的方案数。

1. 球相同,盒相同,可为空。

则有:$dp_{i,j} = dp_{i,j - 1} + dp_{i - 1,j}$。

2. 球相同,盒相同,不可为空。

则有:$dp_{i,j} = dp_{i - j,j}$。

3. 球不同,盒不同,可为空。

则有:$j^i$。

4. 球不同,盒相同,不可为空。

则有:$dp_{i,j} = dp_{i - 1,j} + dp_{i - 1,j - 1}$。

5. 球不同,盒不同,不可为空。

则有:$dp_{i,j} \times {j!}$。

6. 球不同,盒相同,可为空。

枚举盒子数,则有:$dp_{i,j} = dp_{i - 1,j} + dp_{i - 1,j - 1}$。

7. 球相同,盒不同,不可为空。

隔板法,则有:$\tbinom{i - 1}{j - 1}$。

8. 球相同,盒不同,可为空。

则有:$\tbinom{i + j - 1}{j - 1}$。

卢卡斯定理

证明略。代码如下:

1
2
3
4
5
6
7
8
9
10
int calc (int n,int m)
{
if (n < m) return 0;
return 1ll * fac[n] * inv(fac[m]) % MOD * inc (fac[n - m]) % MOD;
}
int lucas (int m,int m)
{
if (!m) return 1;
return 1ll * lucas (n / MOD,m / MOD) * clac (n % MOD,m % MOD) % MOD;
}