数论基础总结

发布时间 2023-12-23 19:54:43作者: _wxr

为确保准确性,本文中的数默认为非负整数


欧拉好闪,拜谢欧拉


素数

基本概念

  • 整数集合: \(2 = \{...,-2,-1, 0, 1, 2, ...\}\)

  • 自然数集合: \(N = \{0, 1, 2, ...\}\)

  • 整除:若 \(a=bk\), 其中 \(a,b,k\) 都是整数, 则 \(b\) 整除 \(a\), 记做 \(b|a\), 否则记做 \(b \nmid a\)

  • 约数:如 \(b|a\)\(b \geq 0\), 则称 \(b\)\(a\) 的约数(因数), \(a\)\(b\) 的倍数。

    • \(1\) 整除任何数, 任何数都整除\(0\)
    • \(a|b\), \(a|c\), 则 \(a|(b+c)\), \(a|(b-c)\)
    • \(a|b\), 则对任意整数 \(c\), \(a|(bc)\)
    • 传递性:若 \(a|b\), \(b|c\), 则 \(a|c\)

素数与合数

  • 素数: \(a>1\) 且只能被平凡约数整除的数。

  • 合数: \(a>1\) 且不是素数的数称为合数。

  • 其他整数( \(0\), \(1\), 负整数)既不是素数也不是合数。

  • 素数有无穷多个, 但分布比较稀疏, 不大于\(n\)的素数约有 \(\frac{n}{ln(n)}\) 个。

判定素数

bool isPrime(int n)
{
	if (n == 1) return false;
	else
	{
		for (int i = 2; i * i <= n; i++)
			if (n % i == 0) return false;
		return true;
	}
}

分解质因数

vector<int> factor(int x)
{
	vector<int> ret;
	for (int i = 2; i * i <= x; i++)
		while (x % i == 0)
		{
			ret.push_back(i);
			x /= i;
		}
	if (x > 1) ret.push_back(x);
	return ret;
}

埃氏筛

bool isPrime[maxn + 10]; // isPrime[i]true表示i为素数
void eratos(int n)
{
	isPrime[0] = isPrime[1] = false;
	for (int i = 2; i <= n; i++)
		isPrime[i] = true;
	for (int i = 2; i * i <= n; i++)
		if (isPrime[i])
		{
			for (int j = i * i; j <= n; j += i)
				isPrime[j] = false;
		}
}

埃氏筛优化质因数分解

bool isPrime[maxn + 10];
int minFactor[maxn + 10]; //记录每个数的最小素因数的数组
void eratos(int n)
{
	isPrime[0] = isPrime[1] = false;
	minFactor[0] = minFactor[1] = -1;
	for (int i = 2; i <= n; i++)
	{
		isPrime[i] = true;
		minFactor[i] = i; //初始化,表示还未找到最小的素因数
	}
	for (int i = 2; i * i <= n; i++)
	{
		if (isPrime[i])
		{
			for (int j = i * i; j <= n; j += i)
			{
				isPrime[j] = false;
				if (minFactor[j] == j) //如果此前尚未找到j的素因数,
					//那么将其设为i
					minFactor[j] = i;
			}
		}
	}
}
vector<int> factor(int x)
{
	vector<int> ret;
	while (x > 1)
	{
		ret.push_back(minFactor[x]);
		x /= minFactor[x];
	}
	return ret;
}

欧拉筛法

void euler_sieve(int n) //每个合数只被它的最小素因数筛除
{
	memset(isprime, true, sizeof(isprime));
	prime[0] = 0; //记录当前素数个数
	for (int i = 2; i <= n; i++)
	{
		if (isprime[i]) prime[++prime[0]] = i; //把素数保存到素数表prime中,并更新素数个数
		for (int j = 1; j <= prime[0] && i * prime[j] <= n; j++)
		{
			isprime[i * prime[j]] = false; //筛除i*prime[j]
			if (i % prime[j] == 0) break;
			//当i中含有素因子prime[j]时中断循环,确保每个数只被它的最小素因子筛除
			//换言之,i 之前被 pri[j] 筛过了
			//由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定也是pri[j] 的倍数 
			//它们都被筛过了,就不需要再筛了,所以这里直接 break掉就好了
		}
	}
}

费马小定理

如果 \(p\) 是素数,且 \(a\)\(p\) 互质,即 \(\gcd(a,p)=1\)

那么 \(a^{p-1} \equiv 1 \pmod p\)

证明:
考虑 \(1a, 2a, 3a, ..., (p-1)a\) 这些数

  1. 他们两两不同余 $ \pmod p$

  2. 他们均与 \(p\) 互质

因此可以得到 \((1a)\times(2a)\times(3a)\times...\times((p-1)a) \equiv 1\times 2\times 3\times...\times (p-1) \pmod p\)

又因为 \(\gcd(1 * 2 * 3 *...* (p-1),p)=1\),所以 \((a^{p-1}) \equiv 1 \pmod p\)

二次探测定理

如果 \(p\) 是素数, \(x\) 是小于 \(p\) 的正整数, 且 \(x^2 \equiv 1 \pmod p\)

那么要么 \(x=1\),要么 \(x=p-1\)

因为 \(x^2 \bmod p = 1\) 相当于 \(p\) 能整除 \(x^2 - 1\),也即 \(p\) 能整除 \((x+1)(x-1)\)

由于 \(p\) 是素数且 \(x<p\) ,那么只可能是 \(x-1\) 能被 \(p\) 整除(此时 \(x=1\))或 \(x+1\) 能被 \(p\) 整除(此时 \(x=p-1\))。

Miller-Rabin素数测试

对于要判断的数\(n\)

  1. 先判断是不是 \(2\),是的话就返回true

  2. 判断是不是小于 \(2\) 的,或合数,是的话就返回false

  3. \(n-1=d*2^R\) ,求出 \(d,R\), 其中 \(d\) 是奇数。

  4. 随机取一个 \(a\),且 \(1<a<n\) 根据费马小定理,如果 \(a^{n-1}≡1 \pmod p\) 那么 \(n\) 就极有可能是素数,如果等式不成立,那肯定不是素数了

  5. 因为 \(n-1=d*2^R\),所以 \(a^{n-1}=a^{d*2^R}=(a^d)^{2^R}\) 所以我们令 \(x=a^d \bmod n\)

  6. 然后是 \(R\) 次循环,每次循环都让 \(y=(x*x) \bmod n, x=y\),这样 \(R\) 次循环之后 \(x=a^{d*2^R}=a^{n-1}\)

  7. 因为循环的时候 \(y=(x*x) \bmod n\),且 \(x\) 肯定是小于 \(n\) 的,正好可以用二次探测定理,

    如果 \(x^2 \bmod n=1\),也就是 \(y\) 等于 \(1\) 的时候,假如 \(n\) 是素数,那么 \(x=1||x=n-1\),如果 \(x \neq 1 \&\& x \neq n-1\)

    那么 \(n\) 肯定不是素数了,返回false

  8. 运行到这里的时候 \(x=an-1\),根据费马小定理,\(x \neq 1\)的话,肯定不是素数了,返回false

  9. 因为 \(Miller-Rabin\) 得到的结果的正确率为 \(75\%\),所以要多次循环步骤 \(4\)~\(8\) 来提高正确率

  10. 循环多次之后还没返回,那么 \(n\) 很大可能是素数了,返回true

bool Miller_Rabin(long long n) //测试n是否是素数
{
	long long a, x, y, d, R = 0;
	if (n == 2)return true;
	if (n < 2 || n % 2 == 0)return false;
	d = n - 1;
	while (d % 2 == 0)
	{
		d = d / 2;    //去掉因子2
		R++;
	}
	for (int i = 1; i <= 10; i++) //进行10次测试,测试次数越多越准确
	{
		a = rand() % (n - 2) + 2; //随机产生[2,N-1]以内的底数a
		x = power_mod(a, d, n); //二分快速幂计算a^d % n
		for (int j = 1; j <= R; j++)
		{
			y = x * x % n;
			if (y == 1 && x != 1 && x != n - 1) return false;
			x = y;
		}
		if (x != 1) return false;
	}
	return true;
}

玄学:

  • \(n ≤ 2^{32}\) 时,只需检测 \(a = 2,7,61\),即可确保正确,即 \(c = 3\)

  • \(n ≤ 2^{64}\) 时,只需检测 \(a = 2,3,5,7,11\),即可确保正确,即 \(c = 5\)

  • \(n\) 特别大时,随机选一个 \(a\) 进行测试,合数通过测试的概率低于 \(\frac{1}{4}\)
    合数通过 \(c\) 轮测试的概率低于 \(\frac{1}{4^c}\),适当设定测试次数即可。

排列组合

\(A_n^m = n (n - 1) (n - 2) ... (n - m + 1) = \frac{n!}{(n - m)!}\)

\(C_n^m = \frac{A_n^m}{m!} = \frac{n!}{m!(n - m)!}\)

\(C_i^j = C_{i - 1}^{j} + C_{i - 1}^{j - 1}\)

for (int i = 0; i <= k; i++)
{
	c[i][0] = 1;
}
for (int i = 1; i <= k; i++)
{
	for (int j = 1; j <= i; j++)		
	{
		c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
	}
}

也可以 \(O(n)\) 计算出 \(1\)\(n\) 每个数的阶乘和阶乘的逆元,每次使用时 \(O(1)\) 计算

最大公约数

基本概念

  • \(a\), \(b\) 是不都为 \(0\) 的整数, \(c\) 为满足 \(c|a\)\(c|b\) 的最大整数, 则称 \(c\)\(a\), \(b\) 的最大公约数, 记为 \(\gcd(a,b)\)\((a,b)\)

  • 最大公约数有如下性质:

    • \(\gcd(a,b) = \gcd(b,a)\)

    • \(\gcd(a,0)= a\)

    • \(\gcd(a,b)= \gcd(-a,b)\)

    • \(\gcd(a,ka)=a\)

    • \(\gcd(a,b) = \gcd(|a|, |b|)\)

    • \(\gcd(an, bn) = n \times \gcd(a, b)\)

    • \(d|a\)\(d|b\), 则 \(d|\gcd(a, b)\)

    • \(\gcd(a,b)= \gcd(a,ka + b)\)

欧几里得算法

int gcd(int a, int b)
{
	return (b == 0) ? a : gcd(b, a % b);
}

扩展欧几里得算法

扩展欧几里德算法是在已知整数 \(a, b\) 情况下求 \(ax+by = \gcd(a,b)\) 的一组整数解 \(x\)\(y\)

对于整数 \(a, b\),必定存在整数对 \(x\)\(y\) 满足 \(a*x+b*y=\gcd(a, b)\)

证明:

\(a * x1+b * y1= \gcd(a, b)\)

\(b * x2 + (a \bmod b) * y2 = \gcd(b, a \bmod b)\)

\(\because\gcd(a,b)=\gcd(b,a \bmod b)\)

\(\therefore a * x1 + b * y1 = b * x2 + (a \bmod b) * y2\)

\(\therefore a * x1 + b * y1 = b * x2 + (a - b * \lfloor \frac{a}{b} \rfloor) * y2\)

\(\therefore a * x1 + b * y1 = b * x2 + a * y2 - b * \lfloor \frac{a}{b} \rfloor * y2\)

\(\therefore a * x1 + b * y1 = a * y2 + b * (x2 - \lfloor \frac{a}{b} \rfloor * y2)\)

观察上式可知,对于 \((a,b)\) 的不定方程可以转换为 \((b,a \bmod b)\) 的不定方程

\(x1 = y2, y1 = x2 - \lfloor \frac{a}{b} \rfloor * y2\)

由此可知 \(x1, y1\) 可由 \(x2, y2\) 得出来的,由此类推 \(x2, y2\) 可是由 \(x3, y3\) 得出来的,

那什么时候是终止呢?也就是递归 \(\gcd(a,b)\)\(b=0\) 时即 \(\gcd(a,0)\) 此时由 \(a*x+b*y==\gcd(a,b)\)

可知 \(a*x+b*y=a\) 解出 \(x=1, y=0\); 此时就是递归终止的地方

然后根据 \(x1=y2,y1=x2-a/b*y2\) 回溯回去就可求出 \(x\)\(y\) 最初的解了。

所以:\(a*x+b*y=\gcd(a,b)\) 一定有解

int exgcd(int a, int b, int &x1, int &y1) ////求解a*x1+b*y1=gcd(a,b)
{
	int d, x2, y2;
	if (b == 0)
	{
		x1 = 1;
		y1 = 0;
		return a;
	}
	d = exgcd(b, a % b, x2, y2); //求解b*x2+(a%b)*y2=gcd(b,a%b)
	x1 = y2;
	y1 = x2 - a / b * y2;
	return d;
}

扩欧求解不定方程

如果 \(c \bmod d \neq 0\),那么 \(ax+by=c\) 一定无解。

\(c \bmod d=0\) 时(有解),

先用扩欧求出 \(ax' +by' = d =\gcd(a,b)\) 的一组解 \(x'\)\(y'\)

则方程原来的一组解为 \(x=x' \times c/d, y=y' \times c/d\)

原方程的解有无限多组,因为满足下面式子:

\(a \times (x+kb)+b \times (y-ka)=c\)\(k\) 为整数

于是我们得到 \(ax+by=c\) 的通解:

\(x=x' \times c/d+k \times b/d\)

\(y=y' \times c/d-k \times a/d\)

欧拉函数

对于一个正整数 \(x\),小于 \(x\) 且和 \(x\) 互质的正整数的个数,记做 \(\varphi(x)\)

其中 \(\varphi(1)\) 被定义为 \(1\),但是并没有任何实质的意义

通式1:\(\varphi(x)=x \times (1-1/p_1) \times (1-1/p_2) \times (1-1/p_3) \times … \times (1-1/p_n)\)

其中 \(p_1, p_2, p_3...p_n\)\(x\) 的所有质因数

通式2:若 \(x\) 是质数 \(p\)\(k\) 次幂,即 \(x=p^k\),有 \(\varphi(x) = p^k-p^{k-1} = (p-1)\times p^{k-1}\)

程序中我们一般将 \(\varphi(x)\) 写成 \(phi(x)\)

性质1: 欧拉函数是积性函数

\(x,y\) 互质(即 \(\gcd(x,y)=1\)),那么 \(\varphi(x \times y)=\varphi(x) \times \varphi(y)\)

性质2: 若 \(x\) 是质数 \(\varphi(x) = x-1\)

int euler(int x) //计算小于x的数中与x互质的数的个数
{
	int ans = x, a = x; //找出所有x的质因数
	for (int i = 2; i * i <= a; i++) //从2讨论到sqrt(n)即可
	{
		if (a % i == 0) //i为x的质因数
		{
			ans = ans - ans / i; //φ(x) = x*(1 - 1/p1)*(1 - 1/p2)......
			while (a % i == 0) a = a / i; //算术基本定理,抹掉i^1,i^2,i^3....
		}
	}
	if (a > 1) ans = ans - ans / a; //存在大于sqrt(a)的质因子
	return ans;
}

筛法求欧拉函数

预先置所有数的欧拉函数值都为它本身,由定理可知,如果 \(p\) 是一个正整数且满足 \(\varphi(p)=p-1\);那么 \(p\) 是素数。

在遍历过程中如果遇到欧拉函数与自身相等的情况,那么说明该数为素数,把这个数的欧拉函数值改变,同时也把能被素因子整除的数改变。

void selcet_euler(int n) //求 φ(1) ,φ(2),...,φ(n)
{
	for (int i = 1; i <= n; i++) phi[i] = i;
	for (int i = 2; i <= n; i++)
	{
		if (phi[i] == i) //表示i未被筛到,i是素数
		{
			for (j = i; j <= n; j += i) //然后更新含有它的数
				phi[j] = phi[j] / i * (i - 1); // x*(1 - 1/p1)....*(1 - 1/pk).先除后乘
		} // 此处注意先/i再*(i-1),否则范围较大时会溢出
	}
}

欧拉筛线性求欧拉函数

欧拉\(^{2}\)

利用欧拉函数的性质( \(p\) 为质数 ):

  1. \(\varphi(p)=p-1\)

  2. 如果 \(i \bmod p = 0\), 那么 \(\varphi(i * p)=p * \varphi(i)\)

  3. \(i \bmod p ≠0\), 那么 \(\varphi( i * p )=\varphi(i) * ( p-1 )\)

void getPhi() //求 φ(1) ,φ(2),...,φ(N)
{
	int tot = 0;
    phi[1] = 1;
	for (int i = 2; i <= N; i++)
	{
		if (Mark[i] == false)
		{
			Prime[++tot] = i;    //当 i 是素数时 phi[i]=i-1
			phi[i] = i - 1;
		}
		for (int j = 1; j <= tot && i * Prime[j] <= N; j++)
		{
			Mark[i * Prime[j]] = true;
			if (i % Prime[j] == 0)
			{
				phi[i * Prime[j]] = phi[i] * Prime[j];
				break;
			} //如果i mod p = 0, 那么 phi[i * p]=p * phi[i]
			else phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
		} //其实这里Prime[j]-1就是phi[Prime[j]],利用了欧拉函数的积性
	}
} //时间复杂度O(n)

欧拉定理

\(a,n\) 为正整数,且 \(a,n\) 互质(即 \(\gcd(a,n)=1\)),则有:
\(a^{\varphi(n)} \equiv 1 \pmod n\)

证明:
设小于 \(n\) 且与 \(n\) 互质的数为 \(x_1, x_2,...,x_{\varphi(n)}\)
考虑这些数 \(a*x_1,a*x_2,...,a*x_{\varphi(n)}\)

  1. 他们两两不同余 \(\pmod n\)

  2. 他们均与 \(n\) 互质

因此可以得到

\((a*x_1)*(a*x_2)*(a*x_3)*...*(a*x_{\varphi(n)})=x_1*x_2*x_3*...*x_{\varphi(n)} \pmod p\)

又因为 \(\gcd(x_1*x_2*x_3*...*x_{\varphi(n)},n)=1\),所以 \(a^{\varphi(n)}≡ 1 \pmod n\)

利用欧拉定理降幂:

\(a\)\(n\) 互质时,\(a^b \bmod n = a^{b \bmod \varphi(n)} \bmod n\)

\(b \geq \varphi(c)\) 时,\(a^b \bmod c = a^{b \bmod \varphi(c) + \varphi(c)} \bmod c\)

欧拉反演

欧拉反演

逆元

如果一个线性同余方程 $ax \equiv 1 \pmod p $,则 \(x\) 称为 \(a \bmod p\) 的逆元,记作 \(a^{-1}\)。在 \([0,p)\) 的范围内,逆元是唯一的。

由扩欧可知,只有 \(\gcd(a,p)=1\)\(a\) 的逆元存在

作用: \(a / b \bmod p = a \times b ^ {-1} \bmod p = (a \bmod p) \times (b ^ {-1} \bmod p) \bmod p\)

扩欧求逆元

int inv(int a, int n)
{
	int x, y;
	if (exgcd(a, n, x, y) == 1) return (x + n) % n;
	else return -1;
}

欧拉定理求逆元

\(a^{\varphi(p)} \equiv 1 \pmod p\)

\(a * a ^ {\varphi(p) - 1} \equiv 1 \pmod p\)

特别地,当 \(p\) 为质数时,\(a\) 的逆元为 \(a^{p - 2}\)

快速幂计算即可

线性求逆元

\(k \times a + r \equiv 0 \pmod p\) \((r < a)\)

两边同时乘上 \(a^{-1} \times r^{-1}\)\((k \times a + r) \times a^{-1} \times r^{-1}\equiv 0 \pmod p\)

去括号得 \(k \times r^{-1} + a ^ {-1} \equiv 0 \pmod p\)

移项得 $ a ^ {-1} \equiv -k \times r^{-1} \pmod p$

即 $ a ^ {-1} = -\lfloor \frac{p}{a} \rfloor \times (p \bmod a)^{-1} \bmod p$

即 $ a ^ {-1} = (p-\lfloor \frac{p}{a} \rfloor) \times (p \bmod a)^{-1} \bmod p$

inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++)
	inv[i] = (p - p / i) * inv[p % i] % p;

线性求任意多个数逆元

若需要求 \(a_1, a_2...a_n\)\(n\) 个数的逆元

则设 \(s_i = \prod^{i}_{j=1} a_i\)

再求出 \(s_n\) 的逆元 \(sv_n\)

再依次求出 \(sv_i = sv_{i+1} \times a_{i+1}\)

则有 \(a_i\) 的逆元 \(inv_i = sv_i \times s_{i - 1}\)

其他一些很有用的东西

快速幂

int power_mod(int a, int n, int p)
{
	int ans = 1;
	a %= p;
	while (n)
	{
		if (n & 1) ans = ans * a % p;
		a = a * a % p;
		n >>= 1;
	}
	return ans;
}

快速乘法

long long quick_mul(long long a, long long b, long long mod) //快速计算 (a*b) % mod
{
	long long ans = 0; // 初始化
	while (b) //根据b的每一位看加不加当前a
	{
		if (b & 1) //如果当前位为1
		{
			ans = (ans + a) % mod; //ans+=a
		}
		b = b >> 1; //b向前移位
		a = (a << 1) % mod; //更新a
	}
	return ans;
} //例如:3*19=3*(10011)=(3*2^4+3*2^1+3*2^0)