数论还是比较重要的一部分,需要我们好好掌握
话不多说,直接上算法吧
1.线性筛
这还是比较简单的一部分了吧
线性筛是什么就不多说了,顾名思义即可
先讲讲普通筛
代码如下:
memset(isprime, true, sizeof(isprime));
isprime[1] = false;
for (int i = 2; i * i <= n; i++)
for (int j = i; i * j <= n; j++)
isprime[i * j] = false;
代码很简单,时间复杂度约为
O(n⋅log2log2n)
时间还能凑合,但是遇到
n=107
级别的时候就GG了
所以才要引进线性筛这种神奇的操作……
方法? 每一个数都只被自己的最小质因子筛一次
为什么普通筛时间会爆,因为一个数可能被筛了很多次……
看看线性筛的代码(比较丑陋)
memset(isprime, true, sizeof(isprime));
int len = 0; isprime[1] = false;
for (int i = 2; i <= n; i++) {
if (isprime[i]) prime[++len] = i;
for (int j = 1; j <= len && i * prime[j] <= n; j++) {
isprime[i * prime[j]] = false;
if (i % prime[j] == 0) break;
}
}
这就是线性筛的代码了
如果你认为它只能筛素数,那就大错特错了……
在这里我们引入一些概念
积性函数:当
(a,b)=1
时,满足
f(ab)=f(a)∗f(b)
,则
f
为积性函数
完全积性函数:对于任意数对
常见的积性函数:
欧拉函数
约数个数
约数和
线性筛可以解决积性函数求值的问题
我们拿欧拉函数
对于一个积性函数,我们需要考虑2种情况
1.
n=pk
,
p
为质数
2.
当属于第一种情况的时候,很容易得到
当属于第二种情况的时候,可以通过质因数分解得到
所以线性筛即可
还是看一看具体代码比较好
memset(phi, 0, sizeof(phi));
int len = 0; phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!phi[i]) {
phi[i] = i - 1;
prime[++len] = i;
}
for (int j = 1; j <= len && i * prime[j] <= n; j++) {
int k = i * prime[j];
if (i % prime[j] == 0) {
phi[k] = phi[i] * prime[j];
break;
}
phi[k] = phi[i] * (prime[j] - 1);
}
}
和线性筛素数的代码大同小异
再举个例子,约数和
σ(n)
还是和求
φ(n)
一样的思路,分类讨论两种情况
1.
n=pk
2.
n=pk11×pk22×…×pkmm
(
p1,k1,p2,k2…pm,km
都为正整数)
第一种情况的
σ(n)
很好求
很显然
第二种也差不太多,大同小异
这个和求 φ(n) 的过程差不多,就不写代码了
基本所有的积性函数怎么求值都可以这样分两步求
那么,线性筛这个部分就告一段落了
2.扩展欧几里得
欧几里得算法求最大公约数大家应该都会
时间复杂度约为
O(log2n)
引入一个定理:
对于一个不定方程
ax+by=c
,只有
(a,b)|c
的时候,方程有整数解
推论:若
(a,b)=1
,则
ax+by=1
有解(很显然吧)
那么,扩展欧几里得就是一个可以求得这个方程的其中一组整数解的算法
方程为:
ax+by=c,c=(a,b)
我们考虑使用欧几里得算法的思想,即为辗转相除法
令
a=bq+r,r=a mod b
,递归求出
bx+ry=c
的解
假设我们求出
bx+ry=c
的解为
x′,y′
将
a=bq+r
代入
ax+by=c
,则可以转化为:
所以 x′=xq+y,y′=x
所以原始的解 x=y′,y=x′−y′q
递归求解即可
注意一下边界,当 b=0 时, x=1,y=0
下面放一放代码吧
void calc(int a, int b, int &x, int &y) {
if (b==0) {
x = 1, y = 0;
return;
}
int q = a / b, r = a % b;
calc(b, r, y, x);
y -= q * x;
}
代码确实比较简洁,但是写起来可能还是需要一定的思考和理解的
那么,可能你会问,这鬼东西有什么用呢?
好,那我们来看看
来引进一个十分重要的概念,叫逆元
(1)逆元
若
ax≡1(modb)
,则称
x
是
这个定义式其实等价于
只有当
(a,b)=1
时才会有逆元,否则不存在
我们一般这么写
a
的逆元为
那么,在模
b
意义下,
求逆元直接用扩展欧几里得即可
显然,在
[0,b)
的范围内,
a
关于模
反证法即可
只求一个很容易,那么我们要求
1−n
关于模质数
p
的逆元呢?
并且
我们来推导一下吧
首先,
1−1≡1(modp)
(十分明显)
那么,我们假设
p=ax+b,b<x,1<x<p
把这个式子带进去,就会得到
ax+b≡0(modp)
两边同时乘上
x−1⋅b−1
,可得
所以,逆元就可以推出来了
代码如下
for (inv[1] = 1, i = 2; i <= n; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
逆元这个东西还是十分重要的,在一些求方案数的dp中会大量使用,特别是遇到除法的时候