数学专题

1.扩展欧几里得

扩展欧几里得,是一种用于求不定方程 \(ax + by = \gcd(a, b)\) 的整数解的算法。
要求解方程 \(ax + by = c\),我们需要先知道这个方程是否有解。这就需要用到裴蜀定理。
裴蜀定理:对于两个整数 \(a, b\),设 \(d = \gcd(a, b)\),则方程 \(ax + by = d\) 有整数解。
于是,方程 \(ax + by = c\) 有整数解当且仅当 \(c\)\(\gcd(a, b)\) 的倍数。因为 \(ax\)\(by\) 均为 \(\gcd(a, b)\) 的倍数,所以得到的结果一定是 \(\gcd(a, b)\) 的倍数。因此,当 \(c\) 不为 \(\gcd(a, b)\) 的倍数时,方程 \(ax + by = c\) 一定没有整数解。否则,就一定可以构造一组整数解。
接下来,进入正题。
我们先回顾一下欧几里得算法。
欧几里得算法计算 \(\gcd(a, b)\) 的大致流程为:

  • 如果 \(b = 0\),则返回 \(a\)
  • 如果 \(b \neq 0\),则返回 \(\gcd(b, a \mod b)\)
int gcd(int a, int b) {  // 求a、b的最大公因数 
	if (b == 0) {
		return a;
	} else {
		return gcd(b, a % b);
	}
	// 也可以写成以下形式: 
	// return (b ? gcd(b, a % b) : a);
}

扩展欧几里得算法呢,就是模仿这个流程。
还是按照原来的流程,如果我们算出了不定方程 \(bx + (a \mod b)y = \gcd(b, a \mod b) = \gcd(a, b)\) 的一组整数解 \((x_0, y_0)\),我们设 \(\gcd(b, a \mod b) = \gcd(a, b) = d\),就可以进行如下推导:

\[bx_0 + (a \mod b)y_0 = d \]

\[bx_0 + (a - \lfloor \frac ab \rfloor \times b)y_0 = d \]

\[bx_0 + ay_0 - \lfloor \frac ab \rfloor \times by_0 = d \]

\[ay_0 + b(x_0 - \lfloor \frac ab \rfloor \times y_0) = d \]

于是,我们就可以得到,方程 \(ax + by = \gcd(a, b)\) 的一组特解为 \((y_0, x_0 - \lfloor \frac ab \rfloor \times y_0)\)
然而,边界条件是什么呢?也就是说,当我们要求的不定方程中 \(b = 0\) 了需要怎么样呢?
这时,我们只需要返回 \((1, 0)\) 就好了。因为这样可以保证你的答案的绝对值不会太大。
于是,我们根据以上推导,我们可以写出以下代码:

pair <int, int> exgcd(int a, int b) {  // 第一种写法:返回一个pair 
	if (b == 0) {  // 边界情况 
		return {1, 0};
	}
	pair <int, int> p = exgcd(b, a % b), p2;  //递归处理 
	p2.first = p.second, p2.second = p.first - a / b * p2.first;
	// 根据递归结果计算现在的解 
	return p2;
}

void Exgcd(int a, int b, int &x, int &y) {  // 第二种写法:传值 
	if (b == 0) {  //边界情况 
		x = 1, y = 0;
		return;
	}
	Exgcd(b, a % b, y, x);  // 递归处理, 记得此时暂时 x = y_0, y = x_0
	y -= a / b * x;  // 根据递归结果计算现在的解 
}

2.逆元

本章的逆元,全名应该叫模意义下的乘法逆元,但是,为了方便,我们简称为逆元。
首先,先给出逆元的定义:
逆元定义:有一个数 \(a\) 和模数 \(mod\),现在,如果有一个整数 \(x\) 满足 \(ax \equiv 1 \pmod {mod}\),则我们称 \(x\)\(a\) 在模 \(mod\) 下的乘法逆元。
也就是说,一个数 \(a\) 在模 \(mod\) 下的逆元 \(x\),满足 \(\frac 1a \equiv x \pmod {mod}\)
接下来,来到如何求一个数的最小的正整数逆元。
首先,如果模数 \(mod\) 为质数,则我们可以用费马小定理配合快速幂来计算。
费马小定理:若两个数 \(a, p\) 满足 \(p\) 为质数,且 \(a \perp b\),则有 \(a^{p - 1} \equiv 1 \pmod p\)
因此,当 \(mod\) 为质数时,我们可以得到 \(a \times a^{mod - 2} = a \times a_{mod - 1} \equiv 1 \pmod {mod}\)。因此这时 \(a\) 在模 \(mod\) 下的逆元为 \(a^{mod - 2} \mod mod\)。用快速幂计算即可。

int qpow(int a, int b, int mod) {  // 快速幂的模板 
	int ans = 1;
	while (b) {
		if (b & 1) ans = 1ll * ans * a % mod;
		a = 1ll * a * a % mod;
		b >>= 1;
	}
	return ans;
}

int inv(int a, int mod) {  // 用费马小定理求逆元 
	return qpow(a, mod - 2, mod);
}

切记:用费马小定理求逆元,模数必须要是质数!!!
但是,如果 模数 \(mod\) 不是质数,怎么办呢?
考虑把原方程 \(ax \equiv 1 \pmod {mod}\) 转化一下。我们设 \(ax\)\(mod\)\(k\) 倍加上 \(1\),则我们可以得到不定方程 \(a \times x - k \times mod = 1\),用扩展欧几里得计算 \(x\) 即可。

int inv(int a, int mod) {
	int x = exgcd(a, -mod).first;
	// 由于得到的是一组特解(可能是负数)
	// 因此我们需要给它取模 
	return (x % mod + mod) % mod;
	// 也可以写成以下形式:
	/*
	int x, y;
	Exgcd(a, -mod, x, y);
	return (x % mod + mod) % mod;
	*/ 
}

根据扩展欧几里得算法的有解的条件,我们不难得到,\(a\) 在模 \(mod\) 下的逆元存在的条件是 \(a \perp mod\)
这样,求一个数的逆元的问题解决了,现在我们要考虑求多个数在模同一个数意义下的逆元。
容易想到一种方法,就是给每个数都求一遍逆元。但是如果要求时间复杂度线性呢?
下面给出一种倒推求逆元的方法,步骤如下:

  • 输入长度为 \(n\) 的数列 \(a\) 和模数 \(mod\)
  • 定义一个数列 \(sum\),其中 \(sum_i = \prod_{j = 1}^ia_i \mod mod\),即数列 \(a\) 的前缀积;
  • 计算 \(sum_n\) 在模 \(mod\) 下的逆元;
  • 根据 \(\frac1{sum_i} = \frac1{sum_{i + 1}} \times a_{i + 1}\),求出 \(sum_1 \sim sum_{n - 1}\) 的逆元;
  • 根据 \(\frac1{a_i} = \frac1{sum_i} \times sum_{i - 1}\),计算 \(a_1 \sim a_n\) 的逆元。

然后……就结束了。
模板(P5431) 的代码如下:

#include <iostream>

using namespace std;

typedef long long ll;

const int MAXN = 5e6 + 100;
ll sum[MAXN], a[MAXN], inv[MAXN], p, k, n, invsum[MAXN];

// 由于模数为质数,所以直接费马小定理求逆元
ll qpow(ll a, ll base) { 
	ll ans = 1;
	while (base) {
		if (base & 1) ans = ans * a % p;
		a = a * a % p;
		base >>= 1;
	}
	return ans;
}

inline ll rd() {  // 快读优化 
	ll res = 0;
	char ch = getchar();
	while (!(ch >= '0' && ch <= '9')) ch = getchar();
	res = ch - '0';
	ch = getchar();
	while (ch >= '0' && ch <= '9') {res = res * 10 + ch - '0';ch = getchar();}
	return res;
}

int main() {
	n = rd(), p = rd(), k = rd();
	sum[0] = 1;
	
	for (int i = 1; i <= n; ++i) {
		a[i] = rd();
		sum[i] = sum[i - 1] * a[i] % p;  // 计算前缀积 
	}
	
	invsum[n] = qpow(sum[n], p - 2);  // 计算sum_n的逆元 
	
	for (int i = n - 1; i; --i) {
		invsum[i] = invsum[i + 1] * a[i + 1] % p;  // 倒推求sum数组逆元 
	}
	
	int ki = k;
	int ans = 0;
	
	for (int i = 1; i <= n; ++i) {
		ans += invsum[i] * sum[i - 1] % p * ki % p;  // 计算题目中的式子 
		ans %= p;
		ki = k * ki % p;
	}
	
	cout << ans << '\n';
	return 0;
}

3.中国剩余定理(CRT)

中国剩余定理是用来求解以下同余方程的算法。

\[\begin{cases}x\equiv b_1\pmod{a_1}\\x\equiv b_2\pmod{a_2}\\\dots\\x\equiv b_n\pmod{a_n}\end{cases} \]

这是 模板题
中国剩余定理的算法流程如下:

  • 输入 \(n\) 和数列 \(a, b\)
  • 计算 \(sum = \prod_{i = 1}^na_i\)
  • 计算数组 \(m_i = \frac{sum}{a_i}\)
  • 对于每一个 \(m_i\),计算 \(t_i\)\(m_i\) 在模 \(a_i\) 下的逆元;
  • 最后的答案为 \(\sum_{i = 1}^na_i \times m_i \times t_i\)

代码如下(好久之前的码风了,好丑):

#include <bits/stdc++.h>

#define int __int128  // 这题要用int128 

using namespace std;

inline void read(int &x) {  // 快读
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
    while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }
    x *= f;
}

inline void write(int x) {  // 快写
    if (x < 0) { putchar('-'); x = -x; }
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

pair <int, int> exgcd(int a, int b) {  // 用来求逆元的扩欧 
	if (b == 0) {
		return {1, 0};
	}
	pair <int, int> p = exgcd(b, a % b), p2;
	p2.first = p.second, p2.second = p.first - a / b * p2.first;
	return p2;
}
int ny(int a, int b) {
	int ans = exgcd(a, b).first;
	ans = (ans % b + b) % b;
	return ans;
}
int k, a[60060], b[60060], M = 1, m[60060], t[60060];
signed main() {
	read(k);
	for (int i = 1; i <= k; ++i) {
		read(b[i]); read(a[i]);
        M *= b[i];  // M实际上是上面的sum 
	}
	int ans = 0;
	for (int i = 1; i <= k; ++i) {
		m[i] = M / b[i];  // 根据公式计算 
		t[i] = ny(m[i], b[i]);
		ans += a[i] * m[i] * t[i];
	}
	write((ans % M + M) % M);
	return 0;
}

4.欧拉函数

欧拉函数定义:欧拉函数 \(\varphi(n)\)\(1 \sim n\) 之内与 \(n\) 互质的正整数的个数,即 \(\varphi(n) = \sum_{i = 1}^n[i \perp n]\)
欧拉函数的计算方法:
设一个数 \(n\) 在为依分解定理下的表示方法为 \(n = \prod_{i = 1}^kp_i^{c_i}\),则 \(\varphi(n) = n\prod_{i = 1}^k(1 - \frac 1{p_i})\)
为什么呢?
\(k = 1\) 时,则易知 \(\varphi(n) = n - 1\),符合以上算法;
\(k = 2\) 时,有 \(\frac n{p_1}\)\(p_1\) 的倍数,不与 \(n\) 互质;\(\frac n{p_2}\)\(p_2\) 的倍数,也不与 \(n\) 互质。因此,我们要从 \(n\) 里面减去 \((\frac n{p_1} + \frac n{p_2})\)。但是,如果一个数既是 \(p_1\) 的倍数,也是 \(p_2\) 的倍数,我们减了两次,因此还要加上一个 \(\frac n{p_1p_2}\)。于是,此时 \(\varphi(n) = (n - \frac n{p_1} - \frac n{p_2} + \frac n{p_1p_2}) = n(1 - \frac 1{p_1})(1 - \frac 1{p_2})\)
如果 \(k\) 更大,也可以用相同的方法证明该计算方法的正确性。
因此,我们就可以快速写出 \(O(\sqrt n)\) 计算 \(\varphi(n)\) 的代码:

int get_phi(int n) {
	int ans = n;
	for (int i = 2; i * i <= n; ++i) {  // 枚举n的每个质因子 
		if (n % i == 0) {               // 如果i为n的质因数 
			while (n % i == 0) n /= i;  // 把这个质因数除干净
			ans = ans / i * (i - 1);    // n * (p - 1/p) = n - n / p
		}
	}
	
	if (n > 1) {  // 此时n是一个质数 
		ans = ans / n * (n - 1);
	}
	
	return ans;
}

除了这种方法,我们还可以用线性筛\(O(n)\) 求出 \(\varphi(1) \sim \varphi(n)\)
当然,这种方法需要用到欧拉函数的积性性质,也就是说,欧拉函数是一个积性函数。
积性函数定义:如果有一个数论函数 \(f(n)\),对于所有的 \(a \perp b\),都有 \(f(a)f(b) = f(ab)\),则我们称函数 \(f(n)\) 为数论函数。特别的,如果一个数论函数 \(f(n)\),对于所有正整数 \(a, b\),都有 \(f(a)f(b) = f(ab)\),则我们称函数 \(f(n)\)完全积性函数。
当然,根据 \(\varphi(n) = n\prod_{i = 1}^k(1 - \frac 1{p_i})\),我们可以快速证明欧拉函数的积性性质。
因为若有两个正整数 \(a, b\),满足 \(a \perp b\),则 \(a\)\(b\) 一定没有相同的质因子,因此我们直接把所有的 \(1 - \frac 1{p_i}\) 乘起来就相当于求了 \(\varphi(a)\varphi(b)\)
因此,我们就可以用线性筛来求欧拉函数了。
至于线性筛到底是什么,参考 oi-wiki,这是网址.
由于线性筛会求出每一个数的最小质因子,因此我们可以用这个质因子 \(p\) 和原数 \(n\) 的倍数关系进行讨论,求出 \(\varphi(n)\)

  • 如果 \(p \perp n\),则 \(\varphi(n) = \varphi(p)\varphi(\frac np) = \varphi(\frac np) \times (p - 1)\)
  • 如果 \(\gcd(p, n) \neq 1\),则我们需要先写出 \(n\) 的为依分解形式为 \(\prod_{i = 1}^kp_i^{c_i}\),则因为 \(p\)\(n\) 不互质,所以不用再添加新的 \((1 - \frac 1p)\),因此 \(\varphi(n) = \frac np \times \prod_{i = 1}^k(1 - \frac 1{p_i}) \times p = \varphi(\frac np) \times p\)

以下是线性筛求欧拉函数的代码:

vector <int> primes;
bool vis[MAXN];
int phi[MAXN];

void init(int N) {  // 线性筛求欧拉函数 
	phi[1] = 1;     // 由于线性筛无法筛到1,所以需要提前赋值
	
	for (int i = 2; i <= N; ++i) {
		if (!vis[i]) {  // i为质数 
			primes.push_back(i);
			phi[i] = i - 1;  // phi(i) = i - 1 
		}
		
		for (int prime : primes) {
			vis[i * prime] = 1;
			if (1ll * i * prime > N) {
				break;
			} 
			if (i % prime == 0) {  // 不互质 
				phi[i * prime] = phi[i] * prime; 
			} else {   // 互质 
				phi[i * prime] = phi[i] * (prime - 1);
			}
		}
	}
}

5.组合数学——排列数&组合数

前置芝士:加法原理&乘法原理
排列数 \(A\) 是组合数学中最基础的数了。\(A_n^m\) 表示 \(n\) 个不同的数选出 \(m\) 个组成一个排列的方案数。计算方法也很简单,直接用乘法原理即可计算,\(A_n^m = n \times (n - 1) \times (n - 2) \times \cdots \times (n - m + 1)\),或者 \(A_n^m = \frac{n!}{(n - m)!}\)。特别的,如果 \(n \lt m\) 或者 \(n \lt 0\) 或者 \(m \lt 0\),则 \(A_n^m = 0\)
组合数稍微难一点,特性也多很多。
组合数 \(C_n^m\) 表示从 \(n\) 个数中选出 \(m\) 个数的方案数,\(C_n^m\) 也可以写成 \(\dbinom{n}{m}\)
\(\dbinom{n}{m}\) 如何计算呢?发现 \(\dbinom{n}{m}\) 其实就是 \(A_n^m\) 加入了不考虑顺序的特性。因此,\(\dbinom{n}{m}\) 中的每种选法都包括了 \(m!\)\(A_n^m\) 中的选法。因此 \(\dbinom{n}{m} = \frac{A_n^m}{m!}\),即 \(\frac{n!}{(n - m)!m!}\)。特别的,如果 \(n \lt m\) 或者 \(n \lt 0\) 或者 \(m \lt 0\),则 \(\dbinom{n}{m} = 0\)
组合数的特性、定理非常多。以下是一个经常使用的组合数的递推式

\[\dbinom{n}{m} = \dbinom{n - 1}{m} + \dbinom{n - 1}{m - 1} \]

为什么呢?因为第 \(n\) 个数只有两种选法:不选或选。如果不选,则前 \(n - 1\) 个数中要选 \(m\) 个;如果选,则前 \(n - 1\) 个数只用选 \(m - 1\) 个。因此 \(\dbinom{n}{m} = \dbinom{n - 1}{m} + \dbinom{n - 1}{m - 1}\)。另外,可以发现,这个式子就是杨辉三角的递推式,边界情况也一样,因此组合数实际上就是杨辉三角。
以下是组合数的定理:
二项式定理\((a + b)^n = \sum_{i = 0}^n\dbinom{n}{i} \times a^ib^{n - i}\)
为什么呢?原因很简单,我们只需要把原式展开,得到 \(n\)\((a + b)\) 相乘,因此每一次得到 \(a^ib^{n - i}\) 就是从 \(n\) 个式子中选 \(i\)\(a\),其他全部选 \(b\),因此 \(a^ib^{n - i}\) 的系数就是 \(\dbinom ni\)
Lucas定理:若 \(p\)质数,则 \(\dbinom nm \equiv \dbinom{\lfloor \frac np \rfloor}{\lfloor \frac mp \rfloor} \dbinom{n \mod p}{m \mod p} \pmod p\)
证明的话……我太菜了,不会,详见这里(证明在折叠框里)。不过,一般情况下,你只需要知道有这个结论,不需要详细证明。
模板题:P3807【模板】卢卡斯定理/Lucas 定理
这道题其实还是挺简单的。我们预处理出阶乘和逆元,用来 \(O(1)\) 计算组合数,然后每次询问时用Lucas定理递归处理即可。
代码:

#include <bits/stdc++.h>
#define int long long

int fact[111111], inv[111111];

int qpow(int a, int b, int mod) {  // 费马小定理求逆元 
	int ans = 1;
	while (b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

int C(int n, int k, int mod) {  // O(1)的组合数 
	if (k < 0 || k > n) return 0;
	else return fact[n] * inv[k] % mod * inv[n - k] % mod;
}

int Lucas(int n, int k, int mod) {  // Lucas定理求组合数 
	if (k == 0) return 1;
	else return C(n % mod, k % mod, mod) * Lucas(n / mod, k / mod, mod) % mod;
}

signed main() {
	int T;
	std :: cin >> T;
	
	while (T--) {
		int n, m, p;
		std :: cin >> n >> m >> p;
		fact[0] = 1, inv[0] = 1;
		for (int i = 1; i <= 1e5; ++i) {  // 预处理阶乘和逆元 
			fact[i] = fact[i - 1] * i % p;
			inv[i] = qpow(fact[i], p - 2, p);
		}
		std :: cout << Lucas(n + m, n, p) << std :: endl;
	} 
}

扩展Lucas可以处理模数不为质数的情况,详见oi-wiki(我懒得写了)

6.组合数学——卡特兰数与斯特林数

卡特兰数(Catalan number) 是组合数学中经常出现的数列。这个数列可以解决许多问题:

  • 长度为 \(2n\) 的合法括号序列个数;
  • \(n\) 个数的合法出入栈序列个数;
  • \(n + 2\) 条边的多边形的三角剖分方案数;
  • 从点 \((0, 0)\) 走到点 \((n, n)\) 且不触碰 \(y = x + 1\) 这条直线的路径数量。
  • \(n\) 个数构成的二叉搜索树的方案数。

以上问题的结果都可以用卡特兰数 \(C_n\) 来表示。
卡特兰数有很多种递推式。这里介绍其中两种:
1.\(C_n = \sum_{i = 0}^{n - 1}C_iC_{n - i}\)
2.\(C_n = \frac {4n - 2}{n + 1}C_{n - 1}\)
这两种是最常用的递推式。当然,卡特兰数还有通项公式:

\[C_n = \frac {\dbinom {2n}n}{n + 1} \]

或者:

\[C_n = \dbinom {2n}n - \dbinom{2n}{n - 1} \]

这一章,我们将详细讲解通项公式中的第二个。
我们已经说了,卡特兰数可以表示为从点 \((0, 0)\) 走到点 \((n, n)\) 且不触碰 \(y = x + 1\) 这条直线的路径数量。
首先,从点 \((0, 0)\) 走到点 \((n, n)\) 的路径数量为 \(\dbinom {2n}n\)。因为一共需要走 \(2n\) 步,而这 \(2n\) 步中有 \(n\) 步往上走,\(n\) 步往右走。因此,我们至需要从这 \(2n\) 步中选出 \(n\) 步往上走即可,这样就可以做到不重复,不遗漏
然后,不触碰 \(y = x + 1\) 直线的路径就是所有的路径减去触碰 \(y = x + 1\) 的路径的数量。
而我们设一条路径触碰到了 \(y = x + 1\) 的直线,其第一次相交于点 \((a, a + 1)\)。此时,我们把点 \((a, a + 1)\) 后面的路径全部翻转一遍。因为我们翻转了后面的路径,后面原本会走到点 \((n, n)\),现在,我们把后面的 \(2n - 2a - 1\) 步往上的变成了往右,往右的变成了往上。而原来剩下的步数中有 \(n - a - 1\) 步往上,\(n - a\) 步往右,因此,翻转后编程了 \(n - a\) 步往上,\(n - a - 1\) 步往右,因此翻转后会走到点 \((a + (n - a - 1), (a + 1) + (n - a))\),即点 \((n - 1, n + 1)\)。通过这样的操作,我们把所有的从 \((0, 0)\) 触碰到 \(y = x + 1\) 的路径映射成了一条不同的从 \((0, 0)\)\((n - 1, n + 1)\) 的路径。当然,可以证明,每一条从 \((0, 0)\)\((n - 1, n + 1)\) 的路径都可以用触碰到 \(y = x + 1\) 直线的某一条路径翻转得到。因此,触碰到 \(y = x + 1\) 的路径的数量就是 \(\dbinom{2n}{n - 1}\),因为需要从 \(2n\) 步中选择 \(n - 1\) 步向右。
综上,\(C_n = \dbinom{2n}n - \dbinom{2n}{n - 1}\)
有了这个通项公式,我们可以轻松证明上面的第一条通项公式。
接下来,来到斯特林数(Stirling数)
首先,我们看到第二类斯特林数
第二类斯特林数,主要是用来解决以下问题的:
\(n\) 个无区别的球,\(m\) 个无区别的盒子,不允许盒子为空,求放球的方案数。这个问题的答案是 \(S(n, m)\),或 \(\begin{Bmatrix}n\\ m\end{Bmatrix}\)
递推式:

\[\begin{Bmatrix}n\\ m\end{Bmatrix} = \begin{Bmatrix}n - 1\\ m - 1\end{Bmatrix} + m \times \begin{Bmatrix}n - 1\\ m\end{Bmatrix} \]

为什么呢?
因为你只有两种放球的方法:单独放进一个盒子和放入一个现有的盒子。

  • 如果单独放进一个盒子,则方案数为 \(\begin{Bmatrix}n - 1\\ m - 1\end{Bmatrix}\)
  • 如果放进一个已有的盒子,则方案数为 \(k\begin{Bmatrix}n - 1\\ m\end{Bmatrix}\)

接下来,我们看到第一类斯特林数
第一类斯特林数表示将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空轮换的方案数,记作 \(s(n, k)\)\(\begin{bmatrix}n\\ k\end{bmatrix}\)
递推式:

\[\begin{bmatrix}n\\ k\end{bmatrix} = \begin{bmatrix}n - 1\\ k - 1\end{bmatrix} + (n - 1) \times \begin{bmatrix}n - 1\\ k\end{bmatrix} \]

边界为 \(\begin{bmatrix}n\\ 0\end{bmatrix} = [n = 0]\)
为什么呢?
因为你只有两种方法:

  • 将该新元素置于一个单独的轮换中,共 \(\begin{bmatrix}n - 1\\ k - 1\end{bmatrix}\) 种方案;
  • 将该元素插入到任何一个现有的轮换中,共 \((n - 1) \times \begin{bmatrix}n - 1\\ k\end{bmatrix}\) 种方案。

7.常见积性函数

欧拉函数一章,我们已经讲过了积性函数的定义,也分析了欧拉函数的积性性质,这一章,将会出现许多其它的重要的积性函数。
首先,就是最容易证明积性性质的函数:1函数。
1函数\(\mathbb{1}(n) = 1\)。众所周知,\(1 \times 1 = 1\),因此1函数为完全积性函数。
幂函数\(\text{id}_k(n) = n^k\)。这个也很容易证明。因为 \(x^k \times y^k = (xy)^k\),可以轻松证明,而且也可以发现,它也是一个完全积性函数。
因子个数函数\(d(n) = \sum_{d \mid n}1\),也可以记作 \(\tau(n)\)。以下是因子个数函数为积性函数的证明:
首先,我们知道,一个数 \(n\) 的因子个数计算方法是:设 \(n\)唯一分解定理下的表示方法为 \(n = \prod_{i = 1}^kp_i^{c_i}\),则 \(d(n) = \prod_{i = 1}^k(c_i + 1)\)。因为每一个质因数都可以选 \(0 \sim c_i\) 个,总个数就可以用乘法原理求出来。然后,对于每一个 \(x \perp y\),我们都知道它们没有相同的质因数,然而对于每一个不同的质因数,对原式的贡献是不会相互影响的,因此它也有积性函数的性质。
因子和函数\(\sigma(n) = \sum_{d \mid n}^d\)。证明与刚才的 \(d(n)\) 类似,这里就不给了。

8.集合与容斥

posted @ 2025-08-31 11:21  langni2013  阅读(15)  评论(1)    收藏  举报