简单数论初步及一些筛法
简单数论初步
扩展同余方程欧几里得算法&一阶线性同余方程
对于一阶线性同余方程\(ax\equiv c\pmod{b}\)可以变形为不定方程\(ax+by=c\),当\(\gcd(a,b)\mid c\)时,该方程有解。
对于一般的一阶线性同余方程我们是无法直接求解的,但可以通过拓展欧几里得算法求出通解。
设\(d=\gcd(a,b)\),则可以通过欧几里得先算出\(ax+by=d\)的通解,再转换成我们所需要的方程的解。
由欧几里得可以知,\(\gcd(a,b)=\gcd(b,a\%b)\),而\(a\%b=a-(a/b)\times b\),假设方程的一组解为\(x_1,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)\),
就可以得出\(x=y_1,y=x_1-a/by_1\)。由拓展欧几里得不断递归,就可以得出\(ax+by=d\)的一组解\((x_0,y_0)\),进而可以求出其通解:
代码如下:
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;
}
因为\(ax_0+by_0=d\),两边同时乘以\(c/d\),可以得到
\(a*(c/d*x_0)+b*(c/d*y_0)=c\),
因而\(x_1=c/d*x_0,y_1=c/d*y_0\)是方程的一组解,
所以方程\(ax+by=c\)的通解为
\(\begin{cases} x=x_1+\frac{b}{d} \times k\\y=y_1-\frac{a}{d} \times k\end{cases}\)
要求x的最小正整数解的话,任取一个x值对\(b/d\)取模即可,但是考虑到会存在x为负数的情况,可以用下面的方法
\(X=(x\%m+m)\%m\) (其中\(m=b/d\)),来保证得出的结果一定是最小正整数。
同理,若要求不定方程\(ax+by=c\)中\(y\)的解也同理。
中国剩余定理 \((CRT)\)
对于 \(n\) 个关于 \(x\) 的同余方程
(严格保证任意 \(m_i, m_j\) 互素)
令 \(M=\Pi_{i=1}^nm_i\quad M_i=M/m_i \quad 记t_i=M_i^{-1}\) ,即表示为 \(M_i\) 模 \(m_i\) 意义下的倒数,则 \(M_it_i\equiv 1_\pmod {m_i}\)
可以得到方程组的一个特解: \(x=a_1 M_1 t_1+a_2 M_2 t_2 + \dots + a_n M_n t_n\)
联想前面的 \(exgcd\) ,可以得知任意两个解的差的绝对值的最小值为
\(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\)
(其中 \(d_i\) 为第 \(i\) 个方程变形成不定方程后的两个未知数的最大公约数)
所以 \(x\) 的通解为: \(x=a_1 M_1 t_1+a_2 M_2 t_2 + \dots + a_n M_n t_n+k M\)
#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;
}
扩展中国剩余定理 \((EX\_CRT)\)
基本问题背景与 \(CRT\) 一致,主要是不再保证任意 \(m_i, m_j\) 互素。
方法其实与前者不太一样。
模板:
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\) 在 \(1e^6\) 以内比线性筛更快?)
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.区间素数筛法
利用类似埃氏筛法的原理,筛出前 \(\sqrt b\) 的素数即可,再对每一个素数划去它的倍数,同时也划去 \([a,b)\) 区间内的倍数
剩下的就是素数了
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
typedef long long ll;
bool a[N], ans[N];
ll cnt = 0, pri[N];
ll L, R;
void prime(int n)
{
for (int i = 2; i <= n; ++ i)
if (!a[i])
{
pri[++cnt] = i;
for (int j = i << 1; j <= n; j += i)
a[j] = 1;
}
}
int main()
{
prime(50000); // 把 [2,√(2^31-1)] 的素数筛出来。
cin >> L >> R;
L += (L == 1); // 特判 L = 1 的情况,1 不是素数!
if (L > R)
{
printf ("0\n");
return 0;
}
for (int i = 1; i <= cnt; i++)
{
for (ll j = max(2 * 1ll, (L - 1) / pri[i] + 1) * pri[i]; j <= R; j += pri[i]) // 筛出 L~R 中的素数。
if(j - L >= 0) ans[j - L] = 1;
}
int tot = 0;
for(int i = 0 ; i <= R - L ; ++ i) if(!ans[i]) tot ++;
printf("%d\n" , tot);
return 0;
}
4.以及由线性筛(欧拉筛)拓展出来的对积性函数的筛法
1’ 欧拉函数
原理:
借助欧拉函数的性质对于任意一个数的欧拉函数 \(\varphi(x)\) 令 \(x=np\) ( \(p\) 为质数)当 \(p|n\) 时,\(\varphi(x)=p\varphi(n)\) ,否则 \(\varphi(x)=\varphi(n)\varphi(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‘莫比乌斯函数
由定义
即:只有当一个数 \(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(ij)=f(i)*f(j)\) 且满足当 \(\gcd(i,j)=1\) 时成立,则为积性函数
可以通过欧拉筛求值
数论零碎知识点
1’欧拉降幂
知道公式,求出了欧拉函数值就可以用了
\(A^x\%p=A^{x\%\varphi(p)+\varphi(p)}\%p\)
可见是对欧拉函数的一种应用,这不是废话吗
2‘乘法逆元
定义:如果有 \(ax\equiv 1_\pmod n\) ,则 \(x\) 是 \(\mathrm{mod}\,n\) 意义下 \(a\) 的乘法逆元,记作 \(x=\mathrm{inv} (a)\) 或 \(x=a^{-1}\)
一个数有逆元当且仅当 \(\gcd(a,n)=1\) 且一个数的逆元唯一。
三种求法
1"利用扩展欧几里得:将 \(ax\equiv 1_\pmod n\) 展开得 \(ax+ny=1\) ,若 \(\gcd(a,n)=1\) ,则可以通过 \(exgcd\) 解出 \(x\) ,并调整到正常范围内。
2"利用费马小定理: 已知 \(a^{p-1} \equiv 1_\pmod p\) ,则 \(a*a^{p-2} \equiv 1_\pmod p\)
当 \(p\) 为质数时可以考虑费马小定理
3"欧拉定理:已知 \(a^{\varphi(p)} \equiv 1_\pmod p\) ,可以得到逆元为 \(a^{\varphi(p)-1}\)
适用于模数不是质数的情况