质数

定义

只存在11和它本身两个约数的数。
质数分布较稀疏,对于一个正整数nn,不超过nn的质数大约有ln(n)/n\ln(n)/n个。

质数的判定

1、试除法
若一个正整数nn为合数,则一定存在一个xx,使xnx|nx<=nx<=\sqrt{n}
证明:
由质数的定义可知,一定存在一个正整数yy,使x<=yx<=yxy=nx * y=n
x<=nx<=\sqrt{n}y<=ny<=\sqrt{n},则当且仅当x=y=nx=y=\sqrt{n}时,xy=nx * y=n才成立。故此时x=nx=\sqrt{n}
x>nx>\sqrt{n}y>ny>\sqrt{n},则xy>nx * y>n。故当y>ny>\sqrt{n}时,x<=nx<=\sqrt{n}
综上述,对于每个合数nn,一定存在xx,使xnx|n,且x<=nx<=\sqrt{n}
根据此命题,我们只需扫描[2,n][2,\sqrt{n}]内所有的数,判断其是否是nn的约数即可。

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

时间复杂度O(n)O(\sqrt{n})

2、Eratosthenes筛法
在试除法中,我们通过枚举一个数的约数来判断其是否是质数。而筛法的思想与其相反。对于每个数,我们枚举它的倍数,易证其倍数一定为合数。对于每个合数,我们将其“筛去”,而剩下的数就是质数了。
数论学习笔记
观察上图,可以发现,只需对每个质数xx,从x2x^2开始枚举倍数,并将其筛去,即可得到质数表。

void sieve(int n, bool *prime) {
  memset(prime, true, sizeof is_prime);
  prime[1] = false;
  for (int i = 2; i <= n; ++i) {
    if (!prime[i]) continue;
    for (int j = i * i; j <= n; j += i)
      prime[j] = false;
  }
}

时间复杂度为O(Σp(n/p))O(\Sigma_{p}(n/p)) == O(nloglogn)O(n\log\log n),效率接近线性。
3、线性筛
尽管加了许多优化,Eratosthenes筛法仍然会重复标记合数。可以这样想,如果能够确定每个合数会被哪个质数筛去,就可以在线性时间复杂度内筛除质数表。
于是维护一个vv数组,viv_i表示ii的最小质因数。同时维护一个pp数组,记录质数表。

void sieve(int n, int &pr, int *p, int *v) {
  for (int i = 2; i <= n; ++i) {
    if (v[i] == 0) v[i] = i, p[++pr] = i;
    // 没被筛,是质数
    for (int j = 1; j <= pr; ++j) {
      int k = i * p[j];
      if (i % p[j] == 0 || k > n) break; 
      // 有比p[j]更小的质因子或超出范围
      v[k] = p[j];
    }
  }
}

质因数分解

算数基本定理(唯一分解定理)

任何一个大于1的自然数 NN ,都可以唯一分解成有限个质数的乘积。
N=Πi=1mpiciN = \Pi_{i=1}^m { p_i ^ { c_i } },其中pip_i为质数且pi1&lt;pip_{i-1} &lt; p_icic_i为正整数。
这样的分解叫做NN的标准分解式。

质因数分解算法

1、试除法
对于一个正整数NN,可以证明至多存在一个大于N\sqrt N的质因数。所以只需扫描[2,N][2, \sqrt N]之间的数。对于每个被扫描的数dd,若dd为质数且dnd|n,则将dd除尽,并统计其个数。

void decom(int n, int &m, int *p, int *c) {
  // 分解 n,分解式储存在 p[1..m] 及 c[1..m] 中
  m = 0;
  for (int i = 2, size = sqrt(n); i <= size; ++i)
    if (n % i == 0) {
      p[++m] = i, c[m] = 0;
      for (; n % i == 0; n /= i)
        ++c[m];
    }
  if (n > 1) p[++m] = n, c[m] = 1; // 特判 n 为质数的情况
}

时间复杂度为O(N)O(\sqrt N)
2、短除法
当已经筛出每个数的最小质因数时,可以更加高效的分解质因数。
对于每个正整数NN,易证其最小质因数v(N)v(N)一定在其唯一分解式中。则将NN除以v(N)v(N)后,可迭代处理N/v(N)N / v(N)。边界条件为N=1N = 1

void decom(int n, int &m, int *p, int *c) {
  m = 0;
  for (; n != 1; n /= v[n])
    if (p[m] == v[n]) // 重复,累计个数
      ++c[m];
    else
      p[++m] = v[n], c[m] = 1;
}

约数

定义

略。。。

算数基本定理的推论

对于一个正整数NN
NN的约数个数为Πi=1m(ci+1)\Pi_{i=1}^m ({c_i + 1})
NN的约数和为Πi=1m(Σj=0ci(pi)j)\Pi_{i=1}^m(\Sigma_{j=0}^{c_i}(p_i)^j)

正约数集合

1、试除法
dNd|Nd&lt;=Nd&lt;=\sqrt N,则定有(N/d)N(N/d)|N。即约数总是成对出现的。
因此,只需扫描[1,N][1,\sqrt N]中的数。对于每个数dd,判断其能否整除NN,若能整除,则将ddN/dN/d同时加入NN的约数集合中。当d=Nd=\sqrt N时特判即可。

void calc(int n, int &m, int *d) {
  m = 0;
  for (int i = 1, size = sqrt(n); i <= size; ++i)
    if (n % i == 0) {
      d[++m] = i;
      if (i != size) d[++m] = n / i;
    }
}

时间复杂度为O(N)O(\sqrt N)
推论:一个正整数NN的约数个数上界为N\sqrt N
2、迭代法
[1,N][1,N]中每个数的约数集合,可以使用迭代法。
扫描每个数dd,并将其加入其所有范围内的倍数的约数集合中。

void calc(int n, vector<int> *d) {
  for (int i = 1; i <= n; ++i)
    for (int j = i; j <= n; j += i)
      d[j].push_back(i);
}

时间复杂度为O(Σi=1NN/i)=O(NlogN)O(\Sigma_{i=1}^N N / i)=O(N\log N)
:调和级数Σi=1N1/i=ln(n+1)+λ\Sigma_{i=1}^N 1/i=\ln(n+1)+\lambda,其中λ\lambda为欧拉常数。
推论:[1,N][1,N]中每个数的约数个数总和大约为NlogNN\log N

约数和 & 约数个数和

除法分块

详见P2261 [CQOI2007]余数求和
这是Capella的题解,比较详细。

数论函数

积性函数

线性筛

线性筛可以在线性的时间复杂度内求出一个积性函数的值。
首先要完全理解线性筛素数。
考虑到线性筛时ipji*p_j会被pjp_j筛去,
ii分解,得到p1c1p2c2...pmcmp_1^{c_1}*p_2^{c_2}...p_m^{c_m}
则一定有pjp1p_j\leq p_1
因为当pj=p1p_j=p_1时就已经break了。
而对于一个积性函数ff,要求f(x)f(x),就必须能够快速求出以下函数值

  1. f(1)f(1)
  2. f(p)f(p) (pp为素数)
  3. f(pc)f(p^c) (pp为素数)
    假设已经完成了上述函数值的计算,

欧拉函数

李煜东的《算法竞赛进阶指南》中讲解很详细,这里补充2条性质:
1、除了N=2N=22ϕ(N)2|\phi(N)
2、当NN为奇数时,ϕ(2N)=ϕ(N)\phi(2N) = \phi(N)
例题
P1390 公约数的和
P2158 [SDOI2008]仪仗队

莫比乌斯函数

略。。。

同余

详见李煜东的《算法竞赛进阶指南》。

剩余系

费马小定理

欧拉定理

反演

略。。。

卷积

略。。。

相关文章: