简单数论初步及一些筛法

简单数论初步

扩展同余方程欧几里得算法&一阶线性同余方程

对于一阶线性同余方程\(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)\),进而可以求出其通解:

\[\begin{cases} x=x_0+\frac{b}{d} \times k\\y=y_0-\frac{a}{d} \times k\end{cases} \]

代码如下:

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\) 的同余方程

\[x \equiv {a_1}_\pmod {m_1} \\ x \equiv {a_2}_\pmod {m_2} \\ \vdots \\ x \equiv {a_n}_\pmod {m_n} \]

(严格保证任意 \(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‘莫比乌斯函数

由定义

\[\mu(x)=\begin{cases}1 & n=1\\ (-1)^k & n=p_1p_2 \dots p_k \\ 0 & otherwise \end{cases} \]

即:只有当一个数 \(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}\)

适用于模数不是质数的情况

posted @ 2021-10-10 21:12  circletime  阅读(212)  评论(0)    收藏  举报