简单数论初步
扩展同余方程欧几里得算法&一阶线性同余方程
对于一阶线性同余方程 a x ≡ c ( m o d b ) ax\equiv c\pmod{b} ax≡c(modb)可以变形为不定方程 a x + b y = c ax+by=c ax+by=c,当 gcd ( a , b ) ∣ c \gcd(a,b)\mid c gcd(a,b)∣c时,该方程有解。
对于一般的一阶线性同余方程我们是无法直接求解的,但可以通过拓展欧几里得算法求出通解。
设 d = gcd ( a , b ) d=\gcd(a,b) d=gcd(a,b),则可以通过欧几里得先算出 a x + b y = d ax+by=d ax+by=d的通解,再转换成我们所需要的方程的解。
由欧几里得可以知, gcd ( a , b ) = gcd ( b , a % b ) \gcd(a,b)=\gcd(b,a\%b) gcd(a,b)=gcd(b,a%b),而 a % b = a − ( a / b ) × b a\%b=a-(a/b)\times b a%b=a−(a/b)×b,假设方程的一组解为 x 1 , y 1 x_1,y_1 x1,y1,所以就有 d = gcd ( a , b ) = gcd ( b , a % b ) = gcd ( b , a − ( a / b ) × b ) = b x 1 + ( a − ( a / b ) × b ) y 1 = a y 1 + b ( x 1 − a / b y 1 ) d=\gcd(a,b)=\gcd(b,a\%b)=\gcd(b,a-(a/b)\times b)=bx_1+(a-(a/b)\times b)y_1=ay_1+b(x_1-a/by_1) d=gcd(a,b)=gcd(b,a%b)=gcd(b,a−(a/b)×b)=bx1+(a−(a/b)×b)y1=ay1+b(x1−a/by1),
就可以得出
x
=
y
1
,
y
=
x
1
−
a
/
b
y
1
x=y_1,y=x_1-a/by_1
x=y1,y=x1−a/by1。由拓展欧几里得不断递归,就可以得出
a
x
+
b
y
=
d
ax+by=d
ax+by=d的一组解
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0),进而可以求出其通解:
{
x
=
x
0
+
b
d
×
k
y
=
y
0
−
a
d
×
k
\begin{cases} x=x_0+\frac{b}{d} \times k\\y=y_0-\frac{a}{d} \times k\end{cases}
{x=x0+db×ky=y0−da×k
代码如下:
int exgcd(int a,int b,int& x, int& y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int g = exgcd(b, a % b, y, x);
int temp = x;
x = y;
y = temp - a / b * y;
return g;
}
当然为了减少码量,可以稍微优化一下
int exgcd(int a,int b,int& x, int& y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int g = exgcd(b, a % b, y, x); //不借助中间变量,直接交换 x,y 的值,最终的x即为原来的y,而y也同理
y = y - a / b * x;
return g;
}
因为 a x 0 + b y 0 = d ax_0+by_0=d ax0+by0=d,两边同时乘以 c / d c/d c/d,可以得到
a ∗ ( c / d ∗ x 0 ) + b ∗ ( c / d ∗ y 0 ) = c a*(c/d*x_0)+b*(c/d*y_0)=c a∗(c/d∗x0)+b∗(c/d∗y0)=c,
因而 x 1 = c / d ∗ x 0 , y 1 = c / d ∗ y 0 x_1=c/d*x_0,y_1=c/d*y_0 x1=c/d∗x0,y1=c/d∗y0是方程的一组解,
所以方程 a x + b y = c ax+by=c ax+by=c的通解为
{ x = x 1 + b d × k y = y 1 − a d × k \begin{cases} x=x_1+\frac{b}{d} \times k\\y=y_1-\frac{a}{d} \times k\end{cases} {x=x1+db×ky=y1−da×k
要求x的最小正整数解的话,任取一个x值对 b / d b/d b/d取模即可,但是考虑到会存在x为负数的情况,可以用下面的方法
X = ( x % m + m ) % m X=(x\%m+m)\%m X=(x%m+m)%m (其中 m = b / d m=b/d m=b/d),来保证得出的结果一定是最小正整数。
同理,若要求不定方程 a x + b y = c ax+by=c ax+by=c中 y y y的解也同理。
中国剩余定理 ( C R T ) (CRT) (CRT)
对于
n
n
n 个关于
x
x
x 的同余方程
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
⋮
x
≡
a
n
(
m
o
d
m
n
)
x \equiv {a_1}_\pmod {m_1} \\ x \equiv {a_2}_\pmod {m_2} \\ \vdots \\ x \equiv {a_n}_\pmod {m_n}
x≡a1(modm1)x≡a2(modm2)⋮x≡an(modmn)
(严格保证任意
m
i
,
m
j
m_i, m_j
mi,mj 互素)
令 M = Π i = 1 n m i M i = M / m i 记 t i = M i − 1 M=\Pi_{i=1}^nm_i\quad M_i=M/m_i \quad 记t_i=M_i^{-1} M=Πi=1nmiMi=M/mi记ti=Mi−1 ,即表示为 M i M_i Mi 模 m i m_i mi 意义下的倒数,则 M i t i ≡ 1 ( m o d m i ) M_it_i\equiv 1_\pmod {m_i} Miti≡1(modmi)
可以得到方程组的一个特解: x = a 1 M 1 t 1 + a 2 M 2 t 2 + ⋯ + a n M n t n x=a_1 M_1 t_1+a_2 M_2 t_2 + \dots + a_n M_n t_n x=a1M1t1+a2M2t2+⋯+anMntn
联想前面的 e x g c d exgcd exgcd ,可以得知任意两个解的差的绝对值的最小值为
T = l c m { m 1 d 1 , m 2 d 2 , … , m n d n , } = l c m { m 1 , m 2 , … , m n } = M T=\mathrm { lcm } \{\dfrac{m_1}{d_1},\dfrac{m_2}{d_2},\dots,\dfrac{m_n}{d_n}, \}=\mathrm{lcm} \{m_1,m_2,\dots,m_n\}=M T=lcm{d1m1,d2m2,…,dnmn,}=lcm{m1,m2,…,mn}=M
(其中 d i d_i di 为第 i i i 个方程变形成不定方程后的两个未知数的最大公约数)
所以 x x x 的通解为: x = a 1 M 1 t 1 + a 2 M 2 t 2 + ⋯ + a n M n t n + k M x=a_1 M_1 t_1+a_2 M_2 t_2 + \dots + a_n M_n t_n+k M x=a1M1t1+a2M2t2+⋯+anMntn+kM
#include <bits/stdc++.h>
using namespace std;
//a[]表示余数,m[]表示模数
void extendEuclid(int a, int b, int& x, int& y)
{
if (b == 0)
{
x = 1; y = 0;
return;
}
extendEuclid(b, a % b, y, x);
y -= a / b * x;
}
int ChineseRemainder(int a[], int m[], int n)
{
int x, y, mi, M = 1, ans = 0;
for (int i = 0; i < n; i++)
M *= m[i];
for (int i = 0; i < n; i++)
{
mi = M / m[i];
extendEuclid(m[i], mi, x, y);
ans = (ans + y * mi * a[i]) % M;
}
return (M + ans % M) % M;
}
int main()
{
int n, i;
int a[15], m[15];
while (cin >> n && n)
{
for (i = 0; i < n; i++)
cin >> a[i] >> m[i];
cout << ChineseRemainder(a, m, n) << endl;
}
return 0;
}
扩展中国剩余定理 ( E X _ C R T ) (EX\_CRT) (EX_CRT)
基本问题背景与 C R T CRT CRT 一致,主要是不再保证任意 m i , m j m_i, m_j mi,mj 互素。
方法其实与前者不太一样。
模板:
ll mul(ll a, ll b, ll mod)
{
ll res = 0;
while (b > 0)
{
if (b & 1) res = (res + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return res;
}
ll gcd(ll a, ll b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
ll lcm(ll a, ll b, ll g)
{
return a / g * b;
}
ll exgcd(ll a, ll b, ll& x, ll& y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
ll gcd = exgcd(b, a % b, y, x);
y -= a / b * x;
return gcd;
}
ll excrt()
{
ll x, y, k;
ll M = a[1], ans = m[1];
for (int i = 2; i <= n; i++)
{
ll p = M, q = a[i], c = ((m[i] - ans) % q + q) % q;
ll gcd = exgcd(p, q, x, y), mod = q / gcd;
if (c % gcd != 0) return -1;
x = mul(x, c / gcd, mod);
ans += x * M;
M *= mod;
ans = (ans % M + M) % M;
}
return (ans % M + M) % M;
}
a[N] m[N] 意义与前者一致
素数筛法及应用
1.埃氏筛法(貌似 n n n 在 1 e 6 1e^6 1e6 以内比线性筛更快?)
int vis[N], prime[N];
int is_prime()
{
int index = 0;
for (int i = 0; i <= N; i++)
vis[i] = 1;
vis[1] = 0;
vis[0] = 0;
for (int i = 2; i <= N; i++)
{
if (vis[i])
{
prime[index++] = i;
for (int j = 2 * i; j <= N; j += i)
vis[j] = 0;
}
}
return index;
}
2.线性筛 ( 貌似还挺有用的亚子)
其实更多的是用来求积性函数
int is_prime()
{
int index = 0;
memset(vis, 0, sizeof(vis));
for (int i = 2; i <= N; i++)
{
if (!vis[i])
prime[index++] = i;
for (int j = 0; j < index && i * prime[j] <= N; j++)
{
vis[i * prime[j]] = prime[j]; //通过prime[j]直接写出每个合数的最小质因数,同时也可进行质因数分解
if (i % prime[j] == 0)
break;
}
}
return index;
}
3.区间素数筛法
利用类似埃氏筛法的原理,筛出前 b \sqrt b b 的素数即可,再对每一个素数划去它的倍数,同时也划去 [ a , b ) [a,b) [a,b) 区间内的倍数
剩下的就是素数了
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
typedef long long ll;
ll prime1[N], prime2[N];
ll a, b;
void is_prime(ll a, ll b)
{
memset(prime1, 0, sizeof(prime1));
memset(prime2, 0, sizeof(prime2));
for (ll i = 2; i * i < b; i++)
{
if (prime1[i] == 0)
{
for (ll j = i; j * j < b; j += i)
prime1[j] = 1;
for (ll j = max(2LL, (a + i - 1) / i) * i; j < b; j += i)
//(a + i - 1) / i) * i是第一个比a大的i的倍数
prime2[j -a] = 1;
}
}
}
int main()
{
while (cin >> a >> b)
{
int cnt = 0;
is_prime(a, b);
for (ll i = 0; i < b - a; i++)
if (prime2[i] == 0)
++cnt;
if (a == 1) cnt--; //如果a=1,也会把1算上,所以要减去
cout << cnt << endl;
}
return 0;
}
4.以及由线性筛(欧拉筛)拓展出来的对积性函数的筛法
1’ 欧拉函数
原理:
借助欧拉函数的性质对于任意一个数的欧拉函数 φ ( x ) \varphi(x) φ(x) 令 x = n p x=np x=np ( p p p 为质数)当 p ∣ n p|n p∣n 时, φ ( x ) = p φ ( n ) \varphi(x)=p\varphi(n) φ(x)=pφ(n) ,否则 φ ( x ) = φ ( n ) φ ( p ) \varphi(x)=\varphi(n)\varphi(p) φ(x)=φ(n)φ(p) 恰好符合欧拉筛的过程。
code
int vis[maxn], phi[maxn], prime[maxn];
void eular()
{
int index= 0;
memset(phi, 0, sizeof(0));
phi[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i]) {
prime[index++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < index && i * prime[j] <= N; j++)
{
vis[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
phi[prime[j] * i] = phi[i] * prime[j];
break;
}
else phi[prime[j] * i] = phi[i] * phi[prime[j]];
}
}
}
2‘莫比乌斯函数
由定义
μ
(
x
)
=
{
1
n
=
1
(
−
1
)
k
n
=
p
1
p
2
…
p
k
0
o
t
h
e
r
w
i
s
e
\mu(x)=\begin{cases}1 & n=1\\ (-1)^k & n=p_1p_2 \dots p_k \\ 0 & otherwise \end{cases}
μ(x)=⎩⎪⎨⎪⎧1(−1)k0n=1n=p1p2…pkotherwise
即:只有当一个数
n
(
n
>
1
)
n(n>1)
n(n>1) 的所有质因数次幂均为1时,才是非零数,借助欧拉筛可以判断一个数是否存在某个质因数的次幂超过1。
通过 if(i % p == 0)
即可判断
code
int mu[N], vis[N], prime[N];
void Möbius()
{
int index = 0;
mu[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i])
{
prime[index++] = i;
mu[i] = -1;
}
for (int j = 0; j < index && i * prime[j] < N; j++)
{
vis[i * prime[j]] = i;
if (i % prime[j] == 0)
break;
mu[i * prime[j]] = -mu[i];
//实际上表达式应该为: mu[i * prime[j]] = mu[i] * mu[prime[j]];
}
}
}
2‘ 积性函数
已知函数 f ( x ) f(x) f(x) 满足 f ( i j ) = f ( i ) ∗ f ( j ) f(ij)=f(i)*f(j) f(ij)=f(i)∗f(j) 且满足当 gcd ( i , j ) = 1 \gcd(i,j)=1 gcd(i,j)=1 时成立,则为积性函数
可以通过欧拉筛求值
数论零碎知识点
1’欧拉降幂
知道公式,求出了欧拉函数值就可以用了
A x % p = A x % φ ( p ) + φ ( p ) % p A^x\%p=A^{x\%\varphi(p)+\varphi(p)}\%p Ax%p=Ax%φ(p)+φ(p)%p
可见是对欧拉函数的一种应用,这不是废话吗
2‘乘法逆元
定义:如果有 a x ≡ 1 ( m o d n ) ax\equiv 1_\pmod n ax≡1(modn) ,则 x x x 是 m o d n \mathrm{mod}\,n modn 意义下 a a a 的乘法逆元,记作 x = i n v ( a ) x=\mathrm{inv} (a) x=inv(a) 或 x = a − 1 x=a^{-1} x=a−1
一个数有逆元当且仅当 gcd ( a , n ) = 1 \gcd(a,n)=1 gcd(a,n)=1 且一个数的逆元唯一。
三种求法
1"利用扩展欧几里得:将 a x ≡ 1 ( m o d n ) ax\equiv 1_\pmod n ax≡1(modn) 展开得 a x + n y = 1 ax+ny=1 ax+ny=1 ,若 gcd ( a , n ) = 1 \gcd(a,n)=1 gcd(a,n)=1 ,则可以通过 e x g c d exgcd exgcd 解出 x x x ,并调整到正常范围内。
2"利用费马小定理: 已知 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1_\pmod p ap−1≡1(modp) ,则 a ∗ a p − 2 ≡ 1 ( m o d p ) a*a^{p-2} \equiv 1_\pmod p a∗ap−2≡1(modp)
当 p p p 为质数时可以考虑费马小定理
3"欧拉定理:已知 a φ ( p ) ≡ 1 ( m o d p ) a^{\varphi(p)} \equiv 1_\pmod p aφ(p)≡1(modp) ,可以得到逆元为 a φ ( p ) − 1 a^{\varphi(p)-1} aφ(p)−1
适用于模数不是质数的情况