window.cnblogsConfig = { homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

数论基础

欧拉函数

定义

欧拉函数 \(\phi(n)\) 代表的是 \([1,n]\) 之间与 \(n\) 互质的数量。

公式

\(\phi(n) = n \times (1- \dfrac{1}{p_1})\times (1- \dfrac{1}{p_2})\times (1- \dfrac{1}{p_3}) \times …… \times (1- \dfrac{1}{p_k})\)

其中:\(n\)\(k\) 个质因数,而 \(p_i\) 就是其中的一个质因数。

推导

如何推导

推导的方式要用到容斥原理

欧拉函数 \(\phi(n)\) 代表的是 \([1,n]\) 之间与 \(n\) 互质的数量,但是我们发现比较难想,于是正难则反,直接从不互质来入手,然后减掉就得出结果了。

如果 \(i\) 是不互质,那么 \(i\)\(n\) 必将有共同的质因数。所以,我们可以通过 \(n\) 的每一个质因数进行倍数处理,然后容斥算出有多少是不互质的,因此也就可得出答案了。

推导过程

我们设:\(n\) 的质因数是:{ \(p_1,p_2,……,p_k\) }。

那么 \(p_i\)\([1,n]\) 这个区间中的倍数就有 \(\dfrac{n}{p_i}\)

为了方便推导,我们暂且先设 \(k=3\)

于是就可以得到公式:

  • \(n-\dfrac{n}{p_1}-\dfrac{n}{p_2}-\dfrac{n}{p_3}+\dfrac{n}{p_1 \times p_2}+\dfrac{n}{p_1 \times p_3} + \dfrac{n}{p_2 \times p_3} -\dfrac{n}{p_1 \times p_2 \times p_3}\)

在进行通分之后,我们发现这个式子其实已经等于 \(\phi(n)\) 了。

得证。

代码

定义法

  • 只能处理一个数的 \(\phi\)

  • 思路是一边分解质因数一遍处理欧拉函数

    int get_euler(int n)
    {
        int euler = n;
        for (int i = 2; i <= n / i; i++)
        {
            if (n % i == 0)
            {
                euler = euler / i * (i - 1) //euler = euler * (1 - 1 / i); 
                while (n % i == 0) n /= i;
            }
        }
    
        if (n > 1) euler = euler / n * (n - 1);
        return euler;
    }
    
    

线性筛法

  • 在进行筛法的时候处理欧拉函数

  • 可以在一次线性筛中处理出 \([1,n]\) 中的欧拉函数

    int primes[N], cnt = 0;
    int euler[N];
    bool st[N];
    
    int get_euler(int n)
    {
        euler[1] = 1; // 1的欧拉函数值是1
    
        // 线性筛模板 + 过程中求欧拉函数
        for (int i = 2; i <= n; i++)
        {
            if (!st[i])
            {
                primes[cnt ++] = i;
                euler[i] = i - 1; // (1)式
            }
            for (int j = 0; primes[j] <= n / i; j++)
            {
                st[i *primes[j]] = true;  
                if (i % primes[j] == 0)
                {
                    euler[i * primes[j]] = euler[i] * primes[j]; //(2)式
                    break;
                }
                 euler[i * primes[j]] = euler[i] * (primes[j] - 1);//(3)式
            }
        }
        // 最后,euler数组中存的就是 1 ~ n 每个数的欧拉函数值 
    }
    urn euler;
    }
    
    

一些等式

  • \(n\) 是质数的时候,\(\phi(n) = n-1\)

  • \(n\) 是质数的时候,存在 \(n^k\) 使得 \(\phi(n^k) = (n-1) \times n ^{k-1}\)

  • 积性函数,如果 \(n\)\(m\) 互质,则 \(\phi(n×m)=\phi(n)×\phi(m)\)

参考文献:

费马小定理

如果 \(p\) 是一个质数,\(a\) 不是 \(p\) 的倍数。那么必然存在: \(a^{p-1}\equiv{1}\pmod{p}\)

谁想推啊!所以不推了。(~逃

逆元

逆元的定义

如果一个线性同余方程,\(p\) 为质数,\(a\)\(p\) 互质,那么 \(ax \equiv{1} \pmod{p}\)\(x\) 就是 \(a\)\(p\) 的逆元。

快速幂求逆元

单个求,时间复杂度 \(\log n\) 。适合单个求。

推导

\(\because\) 根据费马小定理我们可以知道:\(a^{p-1}\equiv{1}\pmod{p}\)

\(\therefore\) \(a \times a^{p-2}\equiv{1}\pmod{p}\)

所以什么?很容易看出来 \(x = a^{p-2}\)

注意了,我们是由费马小定理推出 \(x\) 的值的,但是我们不能根据这个式子去推导费马小定理。

代码

记得 #define int long long~

inline ll ksm(int a,int b){
    ll ans = 1;
    while(b){
        if(b & 1) ans = mod(ans * a,p);
        b >>= 1,a = mod(a * a,p);
    }return ans;
}
inline void solve(){//快速幂求逆元 
    cin >> n >> p;
    for(re int i(1);i <= n;++i){
        println(mod(ksm(i,p-2),p));
    }
}

线性求逆元

整体的思想就是利用已知的数据从前往后推,以达到 \(O(n)\) 复杂度的需求,适合求多组数据的题目。

首先,我们有一个 \(1^{-1}\equiv{1}\pmod{p}\)

设:\(p = k \times i + r\),其中 \(1 <r < i < p\) 。换句话说,\(k\)\(p / i\) 的商,\(r\) 是余数。

将这个式子放在 \(\pmod{p}\) 这个意义下,得到 \(k \times i + r \equiv{0\pmod{p}}\)

\(\because k \times i + k \equiv{0\pmod{p}}\)

乘上 \(i^{-1}\)\(r^{-1}\) \(\therefore\) \(k \times r^{-1} + i^{-1} \equiv{0\pmod{p}}\)

移项 $\therefore i^{-1} \equiv -k \times r ^{-1}\pmod{p} $

将,\(k\)\(p / i\) 的商,\(r\) 是余数带入 \(\therefore i^{-1} \equiv -\left\lfloor\dfrac{p}{i}\right\rfloor \times (p \mod i)^{-1} \pmod{p}\)

这样我们就可以利用递推来完成计算逆元的操作了。

注意:因为是存在负数,所以我们取模操作的时候就要 (x + mod) % mod

void solve(){ //线性求逆元 
    ans[1]=1;
    cin >> n >> p;
    cout<<1<<endl;
    for(int i(2);i <= n;++i){
        ans[i] = (-p / i * ans[p % i] + p) % p;
        cout<<(ans[i] + p) % p<<endl;
    }
}

参考文献:

扩展欧几里得exgcd

扩展欧几里得算法是用来求解 \(ax+by=\gcd(a,b)\) 的一种可行解。

推导过程

设:

\(ax_1 + by_1=gcd(a,b)\)

\(bx_2 + (a\mod b)y_2 = gcd(b,a\mod b)\)

\(\because gcd(a,b) = gcd(b,a \mod b)\)

\(\therefore ax_1 + by_1 = bx_2 + (a\mod b)y_2\)

\(\because a\mod b = a-(\left\lfloor\dfrac{a}{b}\right\rfloor \times b)\)

\(\therefore ax_1 + by_1 = bx_2+[a-(\left\lfloor\dfrac{a}{b}\right\rfloor \times b)]y_2\)

\(\therefore ax_1 + by_1 = bx_2+ay_2-(\left\lfloor\dfrac{a}{b}\right\rfloor \times b)y_2\)

\(\therefore ax_1 + by_1 = ay_2+b\times (x_2-\left\lfloor\dfrac{a}{b}\right\rfloor \times y_2)\)

\(\because a=a ,b=b\)

\(\therefore x_1 = y_2 , y_1 = x_2-\left\lfloor\dfrac{a}{b}\right\rfloor \times y_2\)

一直递归迭代即可。

代码

int Exgcd(int a, int b, int &x, int &y) {
  if (!b) {
    x = 1;
    y = 0;
    return a;
  }
  int d = Exgcd(b, a % b, x, y);
  int t = x;
  x = y;
  y = t - (a / b) * y;
  return d;
}

线性同余方程

定义

$ax \equiv b \pmod n $

找出这个方程的可行解。( \(x\) 为未知数 )。

Exgcd 解法

首先:可以将式子变为 \(ax+kn = b\)

其中 \(x\)\(k\) 是未知数。对于整数解的充要条件是 \(\gcd(a,n) | b\) 也就是 \(b \mod gcd(a,n) = 0\)

我们先根据 Exgcd 来找出一组 \(x_0\)\(k_0\)\(ax_0+nk_0=gcd(a,n)\)

然后等式同除 \(gcd(a,n)\),再乘 \(b\)

得到:

\(a\dfrac{b}{gcd(a,n)}x_0 + n\dfrac{b}{gcd(a,n)}k_0 = b\)

那么这就是这个方程的可行解。

当: \(gcd(a,n) = 1\)\(x_0\)\(k_0\)\(ax+kn = b\) 的一组解,那么:

\(x = x_0 + nt\)

\(k = k_0 - nt\)

为啥,我就不知道了。这对于任意整数 \(t\) 都是正确的

一般来说,题目让你求最小解,所以只需要求 \((x \mod t + t) \mod t\)

其中就有:\(t = \dfrac{n}{gcd(a,n)}\)

code

int ex_gcd(int a, int b, int& x, int& y) {
  if (b == 0) {
    x = 1;
    y = 0;
    return a;
  }
  int d = ex_gcd(b, a % b, x, y);
  int temp = x;
  x = y;
  y = temp - a / b * y;
  return d;
}

bool liEu(int a, int b, int c, int& x, int& y) {
  int d = ex_gcd(a, b, x, y);
  if (c % d != 0) return 0;
  int k = c / d;
  x *= k;
  y *= k;
  return 1;
}

中国剩余定理(CRT)

对于 \(k\) 个同余式,求最小非负的 \(x\)

\(i\) 个同余式。

\(x\equiv{a_i\pmod{m_i}}\)

对于这个方程组需要解 \(x\)

题目保证所有 \(m\) 两两互质。

\(M\) 为所有模数的积,模数都是 \(m+i\)\(M_i = M / m_i\)\(t_i \times M_i\equiv{1\pmod{m_i}}\) 也就是一个逆元,所以直接Exgcd 搞定 \(t_i\)。。

\(x\equiv \sum_{i=1}^n{a_iM_it_i} \pmod M\)

这样就可以求得\(x\)的最小解了。

我们要证明 \(x\) 在任意 \(i (1 \le i \le k)\) 条式子中是成立的

\(i\) 不等于 \(j\) 的时候,有 \(M_j\equiv{0\pmod{m_i}}\)

所以 \(c_j \equiv M_j\equiv{0\pmod{m_i}}\)

又有 \(c_i \equiv M_i \times (M_i^{-1} \mod m_i) \equiv 1 \pmod {m_i}\)

所以我们有:

\(x \equiv \sum_{i=1}^n{a_iM_it_i} \pmod {m_i}\)

\(\equiv {a_iM_i\times(m_i^{-1}} \mod {m_i}) \pmod {m_i}\)

\(\equiv a_i \pmod {m_i}\)

奥管他推的对不对,知道公式不就是了!!

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 1e5+5;
ll n;
ll pr[N],A[N];
ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!b){
        x=1,y=0;
        return a;
    }
    ll g = exgcd(b,a%b,y,x);
    y -= a/b*x;
    return g; 
}
ll crt(){
    ll ans=0;
    ll M=1;
    for(int i =1;i<=n;i++) M*=pr[i];
    for(int i = 1;i <= n;i++){
        ll m=M/pr[i];
        ll x,y;
        exgcd(m,pr[i],x,y);
        ll t=  m*x%M*A[i]%M;
        ans = (ans + t) % M;
    }
    return (ans+M) %M;
}
int main(){
    cin >> n;
    for(int i = 1;i <= n;i++){
        scanf("%lld%lld",&pr[i],&A[i]);
    }
    printf("%lld\n",crt());
    return 0;
}

拓展中国剩余定理(EXCRT)

认为还是金钩爷讲的好,证明其实感觉。。。哎算了,就把证明发这了。反正正常用直接套公式。

现在考虑合并两个同余方程的时候该怎么做。

$ \begin{cases} x \equiv b \pmod {a} \ x \equiv B \pmod {A} \end{cases} $

这个可以写作:

$ \begin{cases} x = ay + b \ x = AY + B \end{cases} $

由于\(x\)是一个常量,因此\(ay+b=AY+B\),其中\(a,b,A,B\)都是常数

整理一下后就是\(ay+A(-Y)=B-b\)

通过exgcd,可以获得一组关于\(ay+A(-Y)=\gcd(a,A)\)的解\((y_0,-Y_0)\)

\(a(y_0 \times \frac{B-b}{\gcd(a,A)})+A(-Y_0 \times \frac{B-b}{\gcd(a,A)})=\gcd(a,A) \times \frac{B-b}{\gcd(a,A)}=B-b\)

由于\(x=ay+b\),现在要最小化\(x\),也就是最小化\(y\),即最小化\(t=y_0 \times \frac{B-b}{\gcd(a,A)}\)

由于\(t\)的通解为\(t+\frac{A}{\gcd(a,A)}k\),因此\(t\)的最小值为\(t_0=t \bmod \frac{A}{\gcd(a,A)}\)

于是就可以将这两个同余方程合并为

$ x \equiv a \times t_0+b \pmod {\mathbb{lcm}(a,A)} $

参考文献:https://www.luogu.com.cn/blog/KingSann/solution-p4777

对于洛谷 P4777 的模板代码如下:

signed n;
int A, B, a, b, d, x, y;
void exgcd(int &x, int &y, int a, int b) {
	if (!b) {
		d = a, x = 1, y = 0;
	} else exgcd(y, x, b, a % b), y -= a / b * x;
}
void merge() {
	exgcd(x, y, a, A);
	int c = B - b;
	if (c % d) {
		cout << -1;
		exit(0);
	} else {
		x = ((x * c / d) % (A / d) + (A / d)) % (A / d);
		int m = a * A / __gcd(a, A);
		b = ((a * x + b) % m + m) % m ;
		a = m;
	}
}
void excrt() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		ll x, y;
		cin >> x >> y;
		A = x, B = y;
		if (i != 1) merge();
		else a = x, b = y;
	}
	cout <<	(ll)(b % a);
}
posted @ 2023-12-01 21:57  gsczl71  阅读(107)  评论(0编辑  收藏  举报