数学与数论

\[\color{red}{\texttt{数论和数学都好难/kk}} \]

白皮书第 6 章,进阶指南 0x03。

0xFF 一些链接

\(\color{red}\texttt{【数学2-1】进阶数论}\) | \(\color{red}\texttt{《算法竞赛进阶指南》第四章—数学知识}\) | \(\color{red}\texttt{OI-wiki-数学}\)

0x00 取模

模运算,常用于大数计算,当答案数值非常大时,通常会有两种情况,第一种是取模,通过模上一个较小的数来方便输出和计算;还有一种是高精度,有这个的题目就会比较恶心。

取模有以下性质:

  1. 可加性:\(\texttt{(a + b) mod m = ((a mod m) + (b mod m)) mod m}\)
  2. 可减性:\(\texttt{(a - b) mod m = ((a mod m) - (b mod m)) mod m}\),c++实现时为了避免出现负数,通常要在括号中加上模数 \(m\)
  3. 可乘性:\(\texttt{(a}\, \times\, \texttt{b) mod m = ((a mod m)}\, \times \, \texttt{(b mod m)) mod m}\)
  4. 没有可除性!除法取模等于乘除数的逆元。

0x01 乘法取模

两数直接乘,可能导致爆 long long 导致寄掉(噢当然你可以用 __int128,数据实在太大则考虑是否需要使用高精度),这里给出一个不一定在所有情况下都有用的解决办法。

原理:\(x \times y\) 会爆,可以考虑变成 \((x \times 2) \times (y\ /\ 2)\),然后变成 \((x \times 2^2) \times (y\ /\ 2^2)\),以此类推,直到 \(y = 0\),详细证明见白皮书第 \(389\) 页。

如果 \(x\) 还是不幸爆炸了,那么就需要考虑高精度,但这个做法基本上可以规避掉大部分情况。

using ll = long long;

ll mul (ll x, ll y, ll mod) { // (x * y) % mod
  x %= mod, y %= mod; // 首先规避一下
  ll res = 0;
  while (y) {
    if (y & 1) { // 判断奇偶性
      res = (res + x) % mod;
    }
    x = x * 2 % mod, y >>= 1;
  }
  return res;
}

0x10 快速幂

OI-wiki-快速幂

计算 \(x^n \bmod m\),朴素算法当然是一个个数去乘,边乘边取模,时间复杂度 \(O(n)\),十分不优秀。

一种做法是分治法,看白皮书去。

另一种就是位运算,时间复杂度 \(O(\log n)\),十分优秀,是标准的做法。


举个例子,求 \(a^{11}\ =\ ?\),朴素算法当然能做,但肯定不会去讲。

\(a^{11}\) 分解成 \(a^8 \times a^2 \times a\),每个数的幂次都是 \(2\) 的倍数。 那么就可以方便地计算了。

求幂次,用二进制分解,\(11 = (1011)_2\),跳过 \(a^4\) 即可。

具体实现看代码。

using ll = long long;

ll qmod (ll x, ll y, ll mod) { // x ^ y % mod
  ll sum = 1;
  while (y) {
    if (y % 2) {
      sum = (sum * x) % mod;
    }
    y >>= 2, x = (x * x) % mod;
  }
  return sum;
}

0x20 gcd 和 lcm

OI-wiki-最大公约数

最大公约数和最小公倍数研究整除等问题,也可以用在概率问题的分数通分等等。


备注:

  • 最大公约数,\(\texttt{Greatest Common Divisor(GCD)}\)\(\texttt{Greatest Common Denominator(GCD)}\)\(\texttt{Greatest Common Factor(GCF)}\)\(\texttt{Highest Common Factor(HCF)}\)
  • 最小公倍数,\(\texttt{Least Common Multiple(LCM)}\)

0x21 GCD

整数 \(a\)\(b\) 的最大公约数,就是一个最大的整数 \(c\)\(c \mid a\)\(c \mid b\),记作 \(\gcd(a,b)\)

注意,因为是最大公约数,而 \(\left\vert a\right\vert\) 又是 \(a\) 的约数(\(a\) 无论是负数或非负数),所以对于任意的 \((a,b)\),有 \(\gcd(a,b) = \gcd(\left\vert a\right\vert, \left\vert b\right\vert)\)

GCD有以下性质:

  1. \(\gcd(a,b) = gcd(a, k \times a + b)\)
  2. \(\gcd(k \times a, k \times b) = k \times \gcd(a,b)\)
  3. 多个整数的 GCD:\(\gcd(a,b,c,d) = \gcd(\gcd(a, b), \gcd(c, d))\),即 GCD 具有交换律和结合律(可以这么叫吧,理解万岁)
  4. \(\gcd(a,b) = d\),则 \(\gcd(\frac{a}{d}, \frac{b}{d}) = 1\),即 \(\frac{a}{d}\)\(\frac{b}{d}\) 互质
  5. \(\gcd(a + c \times b, b) = \gcd(a,b)\)

代码有几种,当然你也可以使用自带的函数 __gcd(a, b),但要确保 \(a \geqslant 0\)\(b \geqslant 0\)

\(1^{\texttt{st}}\)欧几里得算法

辗转相除法,是最常用的写法,唯一的缺点就是取模常数太大了,复杂度参考白皮书。

int gcd (int x, int y) {
  return (y ? gcd(y, x % y) : x);
}

极简版

int gcd (int a, int b) {
  while (a ^= b ^= a ^= b %= a) {
  }
  return b;
}

\(2^{\texttt{nd}}\) 更相减损术

基于性质:\(\gcd(a,b)=\gcd(b,a-b)=\gcd(a,a-b)\)

计算步骤:\(\gcd(a,b)=\begin{cases} a & {a = b}\\\gcd(a-b,b) & {a > b}\\\gcd(a,b - a) & {a < b} \end{cases}\)

int gcd (int x, int y) {
  while (x != y) {
    if (x > y) {
      x -= y;
    } else {
      y -= x;
    }
  }
  return x;
}

更相减损术虽然避免了取模的常数,但在最极端的情况下时间复杂度可以达到 \(O(\max(a,b))\),实在不够优秀。在求 \(\gcd(10^9,1)\) 时就会爆炸。

\(3^{\texttt{rd}}\) Stein 算法

基于更相减损术,对于一些情况进行讨论和优化,具体如下:

  • \(a\)\(b\) 同为偶数,\(\gcd(a,b) = 2 \times \gcd(\frac{a}{2}, \frac{b}{2})\)
  • \(a\)\(b\) 同为奇数,\(\gcd(a,b) = \gcd(\frac{a+b}{2}, \frac{a-b}{2})\)
  • \(a\)\(b\) 偶,根据性质 \(2\) 可得 \(\gcd(a,b)=\gcd(\frac{a}{2}, b)\)
  • \(a\)\(b\) 奇,根据性质 \(2\) 可得 \(\gcd(a,b)=\gcd(a,\frac{b}{2})\)

结束条件仍然是 \(a = b\) 时返回 \(a\)

using ll = long long;

ll gcd (ll a, ll b) {
  if (a < b) {
    return gcd(b, a);
  } else if (a == b) {
    return a;
  }
  if (a % 2 == b % 2) {
    return (a % 2 == 0 ? 2 * gcd(a / 2, b / 2) : gcd((a + b) / 2, (a - b) / 2));
  }
  return (a % 2 == 0 ? gcd(a / 2, b) : gcd(a, b / 2));
}

0x22 LCM

\(\texttt{有 LCM,必有 GCD!!!1}\ \ \ \ \ \ \texttt{——鲁迅}\)

OI-wiki-最小公倍数

推论自己看白皮书,结论就是 \(\texttt{lcm(a,b) = a}\,\times\,\texttt{b / gcd(a,b)}\)

为了防止爆炸,可以先除再乘。

int lcm (int x, int y) {
  return 1ll * x / gcd(x, y) * y;
}

0x23 裴蜀定理

OI-wiki-裴蜀定理

又名贝祖定理(Bézout's lemma),是关于 GCD 的一个定理。

  • 初步定理:若有一个整数二元组 \(\texttt{(x, y)}\),则必然存在一个整数二元组 \(\texttt{(a, b)}\) 使得 \(a \times x + b \times y = \gcd(x,y)\),这个等式被称为 Bézout 等式。
  • 推论:整数 \(a\)\(b\) 互质当且仅当存在整数 \(x\)\(y\) 使得 \(a \times x + b \times y = 1\)

0x30 扩展欧几里得算法

OI-wiki-扩展欧几里得算法

洛谷大佬的 tj 也还挺详细的

简称扩欧(Extended Euclidean algorithm, EXGCD),常用于求 \(ax + by = c\) 的一组特解,过程自己看OI-wiki或白皮书,代码:

int exgcd (int a, int b, ll &x, ll &y) {
  if (!b) {
    x = 1, y = 0;
    return a;
  }
  int d = exgcd(b, a % b, x, y);
  swap(x, y), y -= x * (a / b);
  return d;
}

0x40 同余

OI-wiki-线性同余方程

同余是数论的一个基本理论,是很巧妙的工具,它使人们能够用等式的形式简洁地描述整除关系。相关内容有欧拉定理、费马小定理、扩欧、乘法逆元、线性同余方程、中国剩余定理等等。

0x41 同余的定义

  • 同余的定义:设 \(m\) 为正整数,\(a\)\(b\) 都为整数,若 \(m \mid (a-b)\),则称 \(a\)\(b\)\(m\) 同余。
    • 记作 \(a \equiv b \pmod{m}\)
    • 同样也不用考虑 \(a\)\(b\) 的正负,其实就是要带个绝对值。
  • 剩余系:一个模 \(m\) 完全剩余系是一个整数的集合,使得每个整数都与此集合中的一个整数模 \(m\) 同余,例如 \(\left[0,1,2 \ldots m-1 \right]\) 就是模 \(m\) 完全剩余系,称为模 \(m\) 最小非负剩余的集合,剩余系在线性同余方程中有应用。

0x42 同余的定理及性质

首先,\(a,b,c,d,m\) 均为整数。

\(a \equiv b \pmod{m}\) 当且仅当 \(a \bmod m = b \bmod m\)

把同余式转成等式,即 \(a = b + km\)\(k\) 为整数,这说明了同余方程与线性丢番图方程的关系。

\(m\) 为正整数,则模 \(m\) 有以下性质:

  1. 自反性:\(a \equiv a \pmod{m}\)
  2. 对称性:若 \(a \equiv b \pmod{m}\),则 \(b \equiv a \pmod{m}\)
  3. 传递性:若 \(a \equiv b \pmod{m}\)\(b \equiv c \pmod{m}\),则 \(a \equiv c \pmod{m}\)

同余具有可加可减可乘可乘方性,若 \(a \equiv b \pmod{m}\)\(c \equiv d \pmod{m}\),则:

  1. 可加性:\(a + c \equiv b + c \pmod{m}\),甚至 \(a + c \equiv b + d \pmod{m}\)
  2. 可减性:\(a - c \equiv b - c \pmod{m}\),甚至 \(a - c \equiv b - d \pmod{m}\)
  3. 可乘性:\(ac \equiv bc \pmod{m}\),甚至 \(ac \equiv bd \pmod{m}\)
  4. 同样不存在可除性,除法取模需要逆元。
  5. 同余的幂(可乘方性):对于任意正整数 \(k\)\(a^k \equiv b^k \pmod{m}\)

0x43 一元线性同余方程

一元线性同余方程,即给定 \(a,b,m\),求整数 \(x\) 满足:\(ax \equiv b \pmod{m}\)

研究这个有啥用?\(ax \equiv b \pmod{m}\) 表示 \(ax - b\)\(m\) 的倍数,设为 \(-y\) 倍,则有 \(ax + my = b\),这就是二元线性丢番图方程。。。(胡言乱语)

0x44 乘法逆元

模板题:P3811 【模板】模意义下的乘法逆元

乘法逆元主要有三种求法:扩欧、费马小定理、递推预处理。

扩欧

P1082 [NOIP2012 提高组] 同余方程

  • 优点:可以求出 \(m\) 不为质数时的同余方程解。

  • 缺点:和费马小定理差不多。

\(ax\equiv 1\pmod m\),转为 \(ax+bm = 1\),先求出特解 \(x_0\),则通解为 \(x=x_0+mn\),最小整数解就是 \(((x_0 \mod m) + m) \mod m\)

#include <iostream>

using namespace std;
using ll = long long;

ll x, y, z, w;

void exgcd (ll x, ll y, ll &z, ll &w) {
  if (!y) {
    z = 1, w = 0;
    return ;
  }
  exgcd(y, x % y, w, z), w -= x / y * z;
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> x >> y;
  exgcd(x, y, z, w);
  cout << (z % y + y) % y;
  return 0;
}

费马小定理

  • 优点:复杂度为 \(O(\log m)\),处理较少元素逆元十分迅速。

  • 缺点:当元素个数来到更高数量级时就不行了,或者 \(m\) 不为质数。

费马小定理,大致就是说 \(x^{m-1} \equiv 1 \pmod{m}\),那么可以推出 \(\frac{1}{x}\equiv x^{m-2} \pmod{m}\)

用快速幂处理即可。

递推预处理

  • 优点:当需要处理元素在一个 \(O(n)\) 能够处理的范围内时,可以快速求出它们的逆元。

  • 缺点:当元素很大时无能为力,或者 \(m\) 不为质数。

证明自己翻书,代码如下:

const int N = 1e5 + 10;

int inv[N];

inv[1] = 1;

for (int i = 2; i <= n; i++) {
  inv[i] = 1ll * (m - m / i) * inv[m % i] % m;
}

0x60 欧拉函数

欧拉函数(Euler's totient function),即 \(\varphi(n)=\sum\limits_{1\leqslant i\leqslant n}(\gcd(i,n)=1)\),当 \(n\) 为质数时明显有 \(\varphi(n)=n-1\)

0x61 性质

  1. 欧拉函数是积性函数,即当 \(\gcd(a,b)=1\) 时,\(\varphi(a\times b)=\varphi(a)\times\varphi(b)\)
  2. \(n=\sum\limits_{1\leqslant i\leqslant n \& i \mid n} \varphi(i)\),详细证明见 OI-wiki
  3. 详细见 OI-wiki。

0x62 欧拉定理

有个正整数 \(m\),和一个整数 \(a\),若 \(\gcd(a,m)=1\),则有 \(a^{\varphi(m)} \equiv 1 \pmod m\)

0x63 求 \(1\sim n\) 的欧拉函数值

可以在进行欧拉筛的同时计算出每个数的欧拉函数值。

// pri 记录的是质数,phi 记录的是欧拉函数值
f[1] = 1;
for (int i = 2; i <= n; i++) {
  if (!f[i]) {
    pri[++cnt] = i, phi[i] = i - 1;
  }
  for (int j = 1; j <= cnt && i * pri[j] <= n; j++) {
    f[pri[j] * i] = 1, phi[pri[j] * i] = phi[i] * (pri[j] - bool(i % pri[j]));
    if (i % pri[j] == 0) {
      break;
    }
  }
}

0xE0 随手记

0xE1

给出四条边,如何判断是否能够组成梯形。

换句话说,就是梯形的四条边之间有什么关系。

结论:两腰和 > 两底差,两腰差 < 两底差

0xE2 海伦公式

百度百科链接

简单来说,就是给出三角形三边长度 \(a\)\(b\)\(c\),求三角形面积的一个公式。

\(p = \frac{a+b+c}{2}\),那么三角形面积就是 \(\sqrt{p\times(p-a)\times(p-b)\times(p-c)}\)

0xE3 \(C_a^b\)

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10, mod = 1e9 + 7;

int n, inv[N], x[N], f[N], a, b;

int C (int a, int b) {
  return 1ll * f[a] * x[b] % mod * x[a - b] % mod;
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  inv[1] = x[0] = f[0] = 1;
  for (int i = 2; i <= 2e5; i++) {
    inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
  }
  for (int i = 1; i <= 2e5; i++) {
    x[i] = 1ll * x[i - 1] * inv[i] % mod, f[i] = 1ll * f[i - 1] * i % mod;
  }
  return 0;
}
posted @ 2023-05-04 20:40  wnsyou  阅读(36)  评论(0编辑  收藏  举报