OI数论基础(未完工)

OI数论基础

——由\(\bf\color{red}{5}\tt\color{blue}{1}\bf\color{green}{7}\)(就是老师自己的OJ)提供思路

1 素数筛法

1.1 朴素板筛法

将每一个数的所有的倍数都筛去,由于素数只能被1和它本身整除,因此素数不会被筛到,计算量为\(n/2+n/3+n/4+...\)这个调和级数,复杂度\(O(nlogn)\)

const int N = 100010;
bool flag[N];
int p[N],tot;
void init(int n) {
	for(int i=2;i<=b;++i) {
    for(int j=i+i;j<=n;j+=i) {
       flag[j] = true;
      }
    }
  for(int i=2;i<=n;++i) {
  	if(!flag[i]) p[++tot] = i;
  }
}

1.2 优化板筛法

我们注意到朴素版筛法里面 \(36\) 会被 \(2,3,4,6,9,12,18\)\(6\) 个数筛到,相 当于被 \(2*18,3*12,4*9,6*6,9*4,12*3,18*2\) 筛到,实际上 \(a∗b\)\(b∗a\) 是等价的,于是,我们可以通过枚举小的那个因子,来优化一半的计算量, 相当于 \(i∗(i + 1),i∗(i + 2)\) 这样子去筛, 复杂度不变,常数上优化了

void init(int n) { 
  int up = (int)sqrt(1.0*n) + 1; 
  for (int i = 2; i <= up; i++) { 
    for (int j = i * i; j <= n; j += i) { 
      flag[j] = true; 
    } 
  } 
  for (int i = 2; i <= n; i++) { 
    if (!flag[i]) { 
      p[tot++] = i; 
    } 
  } 
}

1.3 线性筛法

那么存在 \(O(n)\) 的筛法,使得每个数只被筛到一次么? 答案是肯定的。 下面这个代码短小精悍,可以做到每一个数只被其最小的素因子筛选到

void init(int n) {
  for (int i = 2; i <= n; i++) { 
    if (!flag[i]) {
      p[tot++] = i;
    }
    for (int j = 0; j < tot; j++) {
      if (i * p[j] > n) { 
        break; 
      } 
      flag[i * p[j]] = true; //p[j]是i * p[j]的最小素因子 
      if (i % p[j] == 0) { // 再往后p[j]就不是i * p[j]的最小素因子了
        break;
      }
    }
  }
}

2 欧几里得

2.1 辗转相减

利用辗转相减法求最大公约数, 即 \(gcd(a,b)\)。假设 \(a > b\), 则 \(gcd(a,b) = gcd(a−b,b)\), 不断的利用大的数减去小的数,就能得到最大公约数。

证明:

\(a = k1 ∗g\)

\(b = k2 ∗g\)

假设 $g $为最大公约数, 那么 \(gcd(k1,k2) = 1\), 设 \(a > b\), 令 \(a = a−b\),

\(a = (k1 −k2)∗g\)

\(b = k2 ∗g\)

我们发现 \(a,b\) 的变化为两个互质的系数在不断相减(始终还是互质), \(g\) 始终是它们的最大公约数,所以当有一个数变成 \(0\) 的时候,另一个数就是 最大公约数

while (a != b) {
  if(a > b) {
    a -= b; 
  } else {
    b -= a;
  }
}

2.2 辗转相除

我们发现当 \(a\) 一直大于 \(b\) 的时候,就会一直减去 \(b\),其实就是变成 \(a\%b\), 于是我们可以利用辗转相除来优化,

while (a && b) {
  if (a > b) {
    a = a % b;     
  } else { 
    b = b % a;     
  } 
}//循环结束后 max(a,b) 就是答案 
int gcd(int a, int b) {return !b ? a : gcd(b, a % b);}

3 拓展欧几里得

扩展欧几里得的典型应用是解决形如 \(a∗x + b∗y = c\) 的二元一次方程 的解的存在性问题以及求出特解和通解。

3.1 解的存在性问题

其实我们通过辗转相减就可以观察出来,\(a\),\(b\) 辗转相减的时候相当于 \(a∗x+b∗y\) 中的 \(x\),\(y\) 在不断变化的过程, 比如 \(a−b\),\(a−2∗b\),\(3∗b−a\),我们 通过前面知道这个过程一定能得到最大公约数, 所以 \(a∗x+b∗y = gcd(a,b)\) 一定有解,那么是否 \(gcd(a,b)\neq c\) 的时候就无解呢?首先我们知道 \(c\)\(gcd(a,b)\) 的倍数才可能有解,因为左边一定含有因子 \(gcd(a,b)\),左边等于右 边,那么右边也一定含有 \(gcd(a,b)\),因此我们可以通过两边同时除以最大公 约数得到一个新的式子

\(k1 ∗x + k2 ∗y = c/g. gcd(k1,k2) = 1\)

由辗转相减可得:

\(k1∗x+k2∗y = 1\) 一定有解,所以

\(k1∗x+k2∗y = c/g\) 一定有解,

所以当 \(c\)\(gcd(a,b)\) 的倍数的时候,方程一定有解

3.2 特解与通解

所以我们只需要求解形如

$a∗x + b∗y = gcd(a,b) $

然后将其扩大\(c/gcd(a,b)\)倍就可以解出原方程的解了

观察到这个式子的本质其实就是\(a\)\(b\)辗转相减的过程,假设已经求出了

\((a-b) * x_1+ b * y = gcd(a,b)\)

的解\(x_1,y_1\),那么变换一下得到\(a* x_1 +b * (y_1-x_1) = gcd(a,b)\),我们可以得出

\(x = x_1, y = y_1 - x_1\)

得到了原方程的解,所以我们可以将原问题变成一个规模更小的子问题,然
后根据子问题的答案推出原问题的答案,考虑到这个辗转相减可以用辗转
相除来替代,我们可以将原问题变成求解

\(b ∗ x_1 + a\%b ∗ y_1 = gcd(a, b)\)

\(a\%b = a - a/b ∗ b\)

代入得

\(b ∗ x_1 + (a - a/b ∗ b) ∗ y_1 = gcd(a, b)\)

等价于

\(a ∗ y_1 + b ∗ (x_1 - a/b ∗ y_1) = gcd(a, b)\)

得到

\(x = y_1, y = x_1 - a/b ∗ y_1\)

因此我们只需一直缩小问题规模,直到变成 \(gcd(a, b) ∗ x + 0 ∗ y = gcd(a, b)\),
然后得到一组特解 \((1, 0)\),再将这组特解反推回去,得到一开始的方程的一
组特解。这个过程可以用一个递归函数来实现。

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

函数执行完毕后代码里面的 \((x, y)\) 就是 \(a ∗ x + b ∗ y = gcd(a, b)\) 的一组
特解, 通解的变化规律就是 \(x\)\(y\) 的值往相反的方向变化,比如 \(x\) 变大一
些,\(y\) 变小一些,使得答案不变,设这个变化的最小单位值为 \(d1\), \(d2\)

\(a ∗ (x + d_1) + b ∗ (y - d_2) = gcd(a, b)\)

\(a ∗ d_1 = b ∗ d_2\)

两边同除 \(gcd(a, b)\)\(k_1 = a/gcd(a, b)\), \(k_2 = b/gcd(a, b)\)

\(k_1 ∗ d_1 = k_2 ∗ d_2\)

这个时候 \(k_1\), \(k_2\) 互质,显然 \(d_1 = k_2\), \(d_2 = k_1\),可得通解为

\((x + k ∗ d_1, y - k ∗ d_2)\)

\((x + k ∗ b/gcd(a, b), y - k ∗ a/gcd(a, b))\)

3.3 关于解的绝对值大小

我们通过递归函数的实现可以观察到,解的绝对值是跟 a, b 的绝对值
同一个级别的

4 排列组合

4.1 排列的定义

\(n\)个不同元素中,任取\(m\)\(m\leq n\),\(m\)\(n\)均为自然数, 下同)个元素按照一定的顺序排成一列,叫做从\(n\)个不同元素中取出\(m\)个元素的一个排列

\(n\)个不同元素中取出\(m\)(\(m\leq n\))个元素的所有排列的个数,叫做从\(n\)个不同元素中取\(m\)个元素的排列数,用符号\(A_n^m\) 表示。

计算公式

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

4.2 组合的定义

\(n\)个元素中选 \(m\) ( \(m\leq n\) ) 个出来,不排序,不在乎顺序就是 \(C^m_n\)

也可表示成 \({n \choose m}\) 的形式。

计算公式

\({C^n_m=\frac{n!}{m!\times (m-n)!}}\)

4.3 基础公式

排列组合的关系为

\(C^m_n×A^m_m=C^m_n×m!=A^n_m\)

对称恒等式

\[{n\choose k} = {n \choose n-k} \]

提取/吸收恒等式

\[{n \choose k} = \frac{n}{k} {n-1 \choose k-1} \]

加法/归纳恒等式

\[{n \choose k} = {n-1 \choose k} + {n-1 \choose k-1} \]

相伴恒等式

\[(n - k) {n \choose k} = k {n-1 \choose k} \]

\[k {n\choose k} = (n - k + 1) {n \choose k+1} \]

上指标反转

\[{n \choose k} = (-1)^k {n - k - 1 \choose k} \]

4.4 二项式定理

\[(a + b) ^ n = \sum_{i=0}^{n}{n \choose i}a^{n-i}b^i \]

4.5二项式反演

\[f_n=\sum_{i=0}^n (-1)^i\times{n\choose i}\times g_i \iff g_n=\sum_{i=0}^n(-1)^i\times{n\choose i}\times f_i\]

常用表达

\[f_n=\sum_{i=0}^n \times{n\choose i}\times g_i \iff g_n=\sum_{i=0}^n(-1)^{n-i}\times{n\choose i}\times f_i \]

4.5.1恰好至多的转换

\(f_i\) 表示恰好的方案数, \(g_i\) 表示至多的方案数,则有

\[g_n = \sum_{i=0}^n\times {n\choose i} \times f_i \]

根据二项式反演得

\[f_n=\sum_{i=0}^n (-1)^{n-i}\times {n\choose i}\times g_i \]

4.5.2恰好至少的转换

\(f_i\) 表示恰好的方案数, \(g_i\) 表示至少的方案数,则有

\[g_k=\sum_{i=k}^n \times {i\choose k} \times f_i \]

根据二项式反演得

\[f_k=\sum_{i=k}^n \times (-1)^{i-k}\times{i\choose k}\times g_i \]

posted @ 2019-12-13 17:12  AxDea  阅读(369)  评论(0编辑  收藏  举报