质数
试除法判定质数
判定数n是否是质数。
如果d是n的因子,那么n/d也是n的因子,$d\leq\frac{n}{d}$,所以$d\leq\sqrt{n}$,故从1枚举到$\sqrt{n}$。
$O(\sqrt{n})$
bool is_prime(int x) {
if (x < 2)return false;
if (x == 2)return true;
for (int i = 2; i <= x / i; i++) { //等价于i*i<=x,i<=sqrt(x)
if (x % i == 0)return false;
}
return true;
}
试除法分解质因数
算术基本定理:所有的整数都可以分解成若干个质因子的乘积。
$n=P_1^{\alpha_{1}}P_2^{\alpha_{2}}…P_i^{\alpha_{i}}$$,P_i$是质数,$\alpha_i>0,n>1$。
$n$中最多只包含一个大于$\sqrt{n}$的质因子,所以只需枚举$[2,\sqrt{n}]$的质因子,然后特判一下$n$最后是否大于1。
$O(\sqrt{n})$
void divide(int x) {
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) {
int s = 0;
while (x % i == 0)x /= i, s++;
cout << i << ' ' << s << endl; // P_i,alpha_i
}
}
if (x > 1)cout << x << ' ' << 1 << endl;// P_i,alpha_i
}
朴素筛法求素数(埃氏筛)
时间复杂度$O(nloglogn)$,缺点:同一个合数可能被多个质数多次标记。
时间复杂度计算:
如果不加continue优化,也就是把所有数的倍数都筛掉。计算次数为$\frac{n}{2}+\frac{n}{3}+…+\frac{n}{n}=n(\frac{1}{2}+\frac{1}{3}+…+\frac{1}{n})$。
$f(n)=\frac{1}{2}+\frac{1}{3}+…+\frac{1}{n}$是一个调和级数,当$n->\infty$,$f(n)->lnn+C$,也就是$logn$。
加了continue优化,也就是把所有质数的倍数都筛掉。计算次数为$n(\frac{1}{2}+\frac{1}{3}+\frac{1}{5}+…+\frac{1}{n})$,其中的分母都为质数,则$g(n)=\frac{1}{2}+\frac{1}{3}+\frac{1}{5}+…+\frac{1}{n}$趋近于$loglogn$。
int primes[maxn], cnt; // primes[] 存储所有素数
bool st[maxn]; // st[x] 存储x是否被筛掉
void get_primes(int n) {
memset(st, 0, sizeof st);
st[0] = st[1] = true;
for (int i = 2; i <= n; i++) {
if (st[i])continue; //优化
primes[cnt++] = i;
for (int j = 2 * i; j <= n; j += i) {
st[j] = true;
}
}
}
线性筛法求素数(欧拉筛)
每一个数只会被它最小的质因子筛掉,每个数只被筛掉一次,因此是$O(n)$。
注意到线性筛法求素数的同时也得到了每个数的最小质因子。
int primes[maxn], cnt; // primes[] 存储所有素数
bool st[maxn]; // st[x] 存储x是否被筛掉
//int minP[maxn]; // minP[x] 表示x的最小质因子
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
//minP[i] = i;
}
for (int j = 0; primes[j] <= n / i; j++) { //primes[j]*i<=n //用已经有的质数作为i要乘的数
st[primes[j] * i] = true;
//minP[primes[j] * i] = primes[j];
if (i % primes[j] == 0) {
break;
}
}
}
}
关于代码中的
if (i % primes[j] == 0) {break;}
primes[j]是i的因子,说明i之前被primes[j]筛过了。
由于primes[]里面质数是从小到大的,所以i乘上其他的质数的结果一定会在后面的迭代中被这个primes[j]的倍数筛掉,就不需要在这里先筛一次,所以这里直接break。
约数
试除法求所有约数
$O(\sqrt{n})$
vector<int> get_divisor(int x) {
vector<int> res;//存储x的所有约数
for (int i = 1; i <= x / i; i++) {
if (x % i == 0) {
res.push_back(i);
if (i != x / i)res.push_back(x / i);
}
}
sort(res.begin(), res.end());//把所有约数从小到大排序
return res;
}
约数个数和约数之和
$N=P_1^{\alpha_{1}}P_2^{\alpha_{2}}…P_i^{\alpha_{i}}$
所有约数的个数:$(\alpha_1+1)(\alpha_2+1)…(\alpha_i+1)$
证明:约数$d=P_1^{\beta_{1}}P_2^{\beta_{2}}…P_i^{\beta_{i}}$,其中$\beta_i$取$[0,\alpha_i]$,用乘法原理。
所有约数之和:$(1+P_1+P_1^2+…+P_1^{\alpha_1})(1+P_2+P_2^2+…+P_2^{\alpha_2})…(1+P_1+P_1^2+…+P_i^{\alpha_i})$
证明:约数$d=P_1^{\beta_{1}}P_2^{\beta_{2}}…P_i^{\beta_{i}}$,其中$\beta_i$取$[0,\alpha_i]$。
$$
\begin{aligned}
d=&1+P_2+P_2^2+…+P_2^{\alpha_2}+\\
&P_1+P_1P_2+P_1P_2^2+…+P_1P_2^{\alpha_2}+\\
&P_1^2+P_1^2P_2+P_1^2P_2^2+…+P_1^2P_2^{\alpha_2}+\\
&……\\
&P_1^{\alpha_1}+P_1^{\alpha_1}P_2+P_1^{\alpha_1}P_2^2+…+P_1^{\alpha_1}P_2^{\alpha_2}\\
=&(1+P_1+P_1^2+…+P_1^{\alpha_1})(1+P_2+P_2^2+…+P_2^{\alpha_2})
\end{aligned}
$$
计算:每一个多项式用秦九韶算法。
unordered_map<int, int> ha;//ha[p]=alpha
for (auto i: ha) {
ll p = i.first, t = i.second;
ll tmp = 1;
while (t--)tmp = (tmp * p + 1) % MOD;
ans = ans * tmp % MOD;
}
欧几里得算法(辗转相除法)
最大公约数$(a,b)=(b,a \% b)$
证明:
设$a \% b=a-k*b$,其中$k=\lfloor{a/b}\rfloor$。
若$d$是$a,b$的公约数,则$d|a$且$d|b$,则易知$d|(a-k*b)$,故$d$也是$b,a\%b$的公约数。
若$d$是$b,a \% b$的公约数,则$d|b$且$d|(a-kb)$,则$d|(a-kb+kb)=d|a$,故$d$也是$a,b$的公约数。
因此$a,b$的公约数集合和$b,a \% b$的公约数集合相同,所以它们的最大公约数也相同。一直递归下去直到$b=0$,0和任何数的最大公约数都等于这个数本身。
$O(logn)$
int gcd(int a, int b) {
if (b == 0)return a;
else return gcd(b, a % b);
}
推广到多个数。
$(a,b,c)=((a,b),(b,c))=((b,a \% b),(c,b \% c))=(b,a \% b,c,b \% c)$。
因为$(b,c)=(c,b\%c)$,所以$(b,c,b\%c)=(c,b\%c)$。
所以$(a,b,c)=(a\%b,b\%c,c)$。
更相减损术(辗转相减法)
最大公约数$a\geq{b}, (a,b)=(b,a-b)=(a,a-b)$。
需要高精度时考虑用更相减损术。
欧拉函数
欧拉函数的定义
1~N中与N互质的数的个数被称为欧拉函数,记为$\phi(N)$。
若在算术基本定理中,$N=p_1^{c_{1}}p_2^{c_{2}}…p_i^{c_{i}}$,则:
$\phi(N)=N\frac{p_1-1}{p_1}\frac{p_2-1}{p_2}…\frac{p_i-1}{p_i}$。
证明:
根据欧拉函数的计算式,我们只需要分解质因数,即可顺便求出数$n$的欧拉函数。
$O(\sqrt{n})$。
int phi(int n) { //计算n的欧拉函数
int res = n;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
res = res / i * (i - 1);//先除后乘
while (n % i == 0)n /= i;
}
}
if (n > 1)res = res / n * (n - 1);
return res;
}
筛法求欧拉函数
埃氏筛
根据欧拉函数的计算式,在埃氏筛的过程中,求出1~n的所有数的欧拉函数。
$O(nloglogn)$
void get_eulers(int n) {
phi[1] = 1; //特殊规定
for (int i = 1; i <= n; i++)phi[i] = i;
memset(st, 0, sizeof st);
for (int i = 2; i <= n; i++) {
if (st[i])continue;
phi[i] = i - 1;
for (int j = 2 * i; j <= n; j += i) {
phi[j] = phi[j] / i * (i - 1);
st[j] = true;
}
}
}
线性筛(欧拉筛)
若$p|n$且$p^2|n$,则$\phi(n)=\phi(n/p)*p$。
若$p|n$且$p^2\nmid{n}$,则$\phi(n)=\phi(n/p)*(p-1)$。
证明:
根据欧拉函数以上性质,在线性筛的过程中,求出1~n的所有数的欧拉函数。
$O(n)$
void get_eulers(int n) {
phi[1] = 1; //特殊规定
memset(st, 0, sizeof st);
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes.push_back(i);
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
} else {
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
}
}
欧拉定理和费马小定理
费马小定理
若$p$为素数,$gcd(a,p)=1$,则$a^{p-1}\equiv1\ (mod\ p)$。
另一个形式:对于任意整数$a$,有$a^p\equiv a\ (mod\ p)$。
欧拉定理
若$gcd(a,m)=1$,则$a^{\phi(m)}\equiv 1\ (mod\ m)$。
证明:
构造一个与$m$互质的数列。设$r_1,r_2,…,r_{\phi(m)}$为模$m$意义下的一个简化剩余系,则$ar_1,ar_2,…,ar_{\phi(m)}$也为模$m$意义下的一个简化剩余系。所以$r_1r_2…r_{\phi(m)}\equiv ar_1\cdot ar_2 \cdot \cdot \cdot ar_{\phi(m)} \equiv a^{\phi(m)}r_1r_2…r_{\phi(m)}\ (mod\ m)$,约去$r_1r_2…r_{\phi(m)}$,即可得$a^{\phi(m)}\equiv 1\ (mod\ m)$。
当m为素数时,由于$\phi(m)=m-1$,代入欧拉定理即可得到费马小定理。
快速幂
求$m^k \% p$,时间复杂度$O(logk)$。
int qpow(int m, int k, int p) {
int res = 1 % p, t = m; //视情况取long long
while (k) {
if (k & 1)res = res * t % p;
t = t * t % p;
k >> 1;
}
return res;
}
快速幂求逆元
乘法逆元的定义:
乘法逆元,是指数学领域群G中任意一个元素a,都在G中有唯一的逆元a‘,具有性质a×a’=a’×a=e,其中e为该群的单位元。
模意义下的乘法逆元:
如果$ax\equiv 1\ (mod\ m)$,则$x$称为$a\ mod\ m$的逆元,记作$a^{-1}$。
$a$存在乘法逆元的充要条件是$a$与模数$m$互质。当模数$m$为质数时,$a^{m-2}$即为$a$的乘法逆元。
证明:
$ax\equiv 1\ (mod\ m)$
$a^{m-1}\equiv 1\ (mod\ m)$(费马小定理)
所以$ax \equiv a^{m-1} \ (mod\ m)$,$x\equiv a^{m-2}\ (mod\ m)$。
int inv = qpow(a, m - 2, m);
if (a % m != 0)cout << t;
线性求逆元
$O(n)$求出$1,2,…,n$中每个数关于$p$的逆元。
逆元的递归形式,使用迭代实现:
$$
i^{-1} \equiv \begin{cases}
1, & \text{if } i = 1, \\
-\lfloor\frac{p}{i}\rfloor (p \bmod i)^{-1}, & \text{otherwises}.
\end{cases} \pmod p
$$
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (long long) (p - p / i) * inv[p % i] % p;
}
使用$p-\lfloor\frac{p}{i}\rfloor$来防止出现负数。
使用以上公式,算2 mod p的逆元的快速方法是,mod - mod/2。
线性求任意n个数的逆元
上面的方法只能求1到n的逆元,如果需要求任意给定n个数$(1\leq a_i<p)$的逆元,需使用下面的方法:
首先计算$n$个数的前缀积,记为$s_i$,然后使用快速幂或扩展欧几里得发计算$s_n$的逆元,记为$sv_n$。
$sv_n$乘上$a_n$,得到$sv_{n-1}$。同理依次计算所有的$sv_i$。
$a_i^{-1}$可以用$sv_i \times s_{i-1}$求得。
在$O(n+logp)$内计算出了n个数的逆元。
s[0] = 1;
for (int i = 1; i <= n; i++)s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2, p);
for (int i = n; i >= 1; i--)sv[i - 1] = sv[i] * a[i] % p;
for (int i = 1; i <= n; i++)inv[i] = sv[i] * s[i - 1] % p;
模运算
模$p$运算下的加、减、乘、除、乘方运算。
$(a+b)\%p=(a\%p+b\%p)\%p$
$(a-b)\%p=(a\%p-b\%p+p)\%p$
$(a\times b)\%p=(a\%p)\times (b\%p)\%p$
$(a/b)\%p=(a*b^{-1}\%p)$
$(a^b)\%p=(a^{b\ mod\ \phi(p) +\phi(p)})\%p$(在$b\geq \phi(p)$成立,通常在指数$b$超级大,结合大数取余使用)(扩展欧拉定理的应用)
扩展欧几里得
贝祖定理
对于任意整数$a$,$b$,存在一对整数$x$,$y$,满足$ax+by=gcd(a,b)$。
证明:
若$b=0$,也就是欧几里得算法的最后一步,显然有一对解$x=1,y=0$,使得$a1+00=gcd(a,0)$。
若$b>0$,则$gcd(a,b)=gcd(b,a\%b)$。假设存在一对整数$x,y$满足$b*x+(a\%b)*y=gcd(b,a\%b)$,因为左式$=bx+(a-b\lfloor a/b \rfloor)y=ay+b(x-\lfloor a/b \rfloor y)$,所以令$x’=y,y’=x-\lfloor a/b \rfloor y$,就得到了$ax’+by’=gcd(a,b)$。
对欧几里得算法的递归过程应用数学归纳,贝祖定理成立。
以上的证明过程就是扩展欧几里得算法。扩展欧几里得算法得到$ax+by=gcd(a,b)$的一组特解$x_0,y_0$,并返回$a,b$的最大公约数$d$。
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y, y = z - y * (a / b);
return d;
}
对于一般的方程,$ax+by=c$的通解可以表示为:
$$
x=\frac{c}{d}x_0+k\frac{b}{d} \\
y=\frac{c}{d}y_0-k\frac{a}{d}
$$
通解是所有模$\frac{b}{d}$与$x$同余的整数。因此最小正整数解为$(\frac{c}{d}x_0) \% \frac{b}{d}$(注意要是正余数)。
此方程有解当且仅当$d|c$,其中$d$为$gcd(a,b)$。
线性同余方程
求一个整数$x$,满足$ax\equiv c\ (mod\ b)$,或给出无解。
$ax\equiv c\ (mod\ b)$等价于$ax-c$是$b$的倍数,不妨设为$-y$倍,该方程可以改写为$ax+by=c$。
利用扩展欧几里得算法求解。
求乘法逆元
如果$ax\equiv 1\ (mod\ m)$,则$x$称为$a\ mod\ m$的逆元,记作$a^{-1}$。
$ax+my=1$。
int d = exgcd(a,m,x,y);
//(x%m+m)%m即为所求
中国剩余定理
设$n_1,n_2,…,n_k$是两两互质的整数,对于任意$n$个整数$a_1,a_2,…,a_n$,求方程组的整数解。
$$
\begin{cases}
x &\equiv a_1 \pmod {n_1} \\
x &\equiv a_2 \pmod {n_2} \\
&\vdots \\
x &\equiv a_k \pmod {n_k} \\
\end{cases}
$$
求解步骤:
1.计算所有模数的积$n$。
2.对于第$i$个方程,计算$m_i=\frac{n}{n_i}$,计算$m_i$在模$n_i$意义下的逆元$m_
i^{-1}$,计算$m_im_i^{-1}$(不要对$n_i$取模)。
3.方程组在模$n$意义下的唯一解为:$x=\sum_{i=1}^{k}a_im_im_i^{-1} (mod\ n)$。
证明:
因为$m_i$是除了$n_i$之外所有模数的倍数,所以对于任意$j\neq i$,$a_im_im_i^{-1}\equiv0\ (mod\ n_j)$。又$a_im_im_i^{-1}\equiv a_i\ (mod\ n_i)$。
所以对于每一个方程$i$,代入$x=\sum_{i=1}^{k}a_im_im_i^{-1}$,都能得到$x\equiv a_im_im_i^{-1}\equiv a_i\ (mod\ n_i)$,每一个方程都能成立。
ll a[maxn], r[maxn];//r是模数
ll CRT(int k) {
ll n = 1, ans = 0;
for (int i = 1; i <= k; i++)n = n * r[i];
for (int i = 1; i <= k; i++) {
ll mi = n / r[i], x, y;
//用exgcd求逆元,因为r[i]不一定是素数,所以不要用快速幂求逆元
exgcd(mi, r[i], x, y); //mi*x mod r[i] =1
x = (x % r[i] + r[i]) % r[i];
ans += a[i] * mi * x;
}
ans = (ans % n + n) % n;
return ans;
}
扩展中国剩余定理
扩展:模数不互质的情况
设两个方程分别是 $x\equiv a_1 \pmod {m_1}$、$x\equiv a_2 \pmod {m_2}$;
将它们转化为不定方程:$x=m_1p+a_1=m_2q+a_2$,其中 $p, q$ 是整数,则有 $m_1p-m_2q=a_2-a_1$。
由裴蜀定理,当 $a_2-a_1$ 不能被 $\gcd(m_1,m_2)$ 整除时,无解;
其他情况下,可以通过扩展欧几里得算法解出来一组可行解 $(p, q)$;
则原来的两方程组成的模方程组的解为 $x\equiv b\pmod M$,其中 $b=m_1p+a_1$,$M=\text{lcm}(m_1, m_2)$。
下面证明原来的两方程可以合并为方程$x\equiv b\pmod M$。
对于方程$m_1p-m_2q=a_2-a_1$,求出来一组特解$p_0,q_0$,整个解系为:
$$
p=p_0+k\frac{m_2}{gcd(m_1,m_2)}\\
q=q_0+k\frac{m_1}{gcd(m_1,m_2)}
$$
通过$x=m_1p+a_1$得到$x$的一个特解$x_0=m_1p_0+a_1$。
$x$的通解为:
$$
x=m_1p+a_1\\
=m_1p_0+k\frac{m_1m_2}{gcd(m_1,m_2)}+a_1\\
=x_0+k\frac{m_1m_2}{gcd(m_1,m_2)}\\
=x_0+klcm(m_1,m_2)
$$
所以$x\equiv x_0\ (mod\ lcm(m_1,m_2))$。
多个方程的情况用上面的方法两两合并即可。
$$
\begin{cases}
x &\equiv a_1 \pmod {m_1} \\
x &\equiv a_2 \pmod {m_2} \\
&\vdots \\
x &\equiv a_k \pmod {m_k} \\
\end{cases}
$$
求x的最小非负整数解。
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
ll d = exgcd(b, a % b, x, y);
ll z = x;
x = y, y = z - (a / b) * y;
return d;
}
ll lcm(ll a, ll b, ll d) {
return a / d * b;//先除后乘,防止爆long
}
ll exCRT(int n) {
ll a1, m1, a2, m2, p, q;
cin >> m1 >> a1;
for (int i = 2; i <= n; i++) {
cin >> m2 >> a2;
ll d = exgcd(m1, m2, p, q);
if ((a2 - a1) % d != 0)return -1; //无解
p = p * (a2 - a1) / d;
ll t = m2 / d;
p = (p % t + t) % t;
ll M = lcm(m1, m2, d);
m1 = M;
}
return (a1 % m1);
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1300773281@qq.com