欧拉函数,快速幂,扩展欧几里得,中国剩余定理

1 欧拉函数

欧拉函数的作用:
欧拉定理
若a与n互质则
a Ψ ( n ) ≡ 1 ( m o d n ) a^{\Psi(n)} \equiv 1(mod n) aΨ(n)1(modn)
当n是质数时就有了
a p − 1 ≡ 1 ( m o d n ) ( 费马小定理) a^{p - 1} \equiv 1(mod n) (费马小定理) ap11(modn)(费马小定理)

1.1 公式法求欧拉函数

Ψ ( n ) \Psi(n) Ψ(n)表示1~n中与n互质的数的个数
分解质因数后

证明:用到了容斥原理

  • 从1到N中去掉p1,p2……pk的所有倍数
  • 加上所有pi * pj的倍数(多减了一次的数)
  • 减去所有pi * pj * pk的倍数
  • 加上所有4个质数的倍数
  • ……

1.1.1 模板

时间复杂度是: n \sqrt{n} n (分解质因数是瓶颈)

int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);

    return res;
}

1.1.2 例题

AcWing 873. 欧拉函数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;

int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++)
    {
        if (x % i == 0)
        {
            while (x % i == 0) x /= i;
            res = res / i * (i - 1);// 这里要先除后乘防止乘后爆int
        }
    }
    if (x > 1) res = res / x * (x - 1);
    return res;
}

int main()
{
    cin >> n;
    while (n -- )
    {
        int x;
        scanf("%d", &x);

        printf("%d\n", phi(x));
    }
    return 0;
}

1.2 筛法求欧拉函数

利用线性筛法求出1~n每一个数的欧拉函数
当primes[j] 是 i 的最小质因子是(在满足(i % primes[j] )的break条件里)
推导过程
e u l e r [ i ] = i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) euler[ i ] = i * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k}) euler[i]=i(1p11)(1p21)...(1pk1)
e u l e r [ i ∗ p r i m e [ j ] ] = ( i ∗ p r i m e [ j ] ) ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) euler[i * prime[j]] = (i * prime[j]) * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k}) euler[iprime[j]]=(iprime[j])(1p11)(1p21)...(1pk1)
两式就多一项prime[j]

当primes[j]不是最小质因子时要在判断是否是之后
e u l e r [ i ] = i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) euler[ i ] = i * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k}) euler[i]=i(1p11)(1p21)...(1pk1)

e u l e r [ i ∗ p r i m e [ j ] ] = ( i ∗ p r i m e [ j ] ) ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) ∗ ( 1 − 1 p r i m e [ j ] ) euler[i * prime[j]] = (i * prime[j]) * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k}) * (1 - \frac{1}{prime[j]}) euler[iprime[j]]=(iprime[j])(1p11)(1p21)...(1pk1)(1prime[j]1)
p r i m e [ j ] ∗ ( 1 − 1 p r i m e [ j ] ) prime[j] * ( 1 - \frac{1}{prime[j]}) prime[j](1prime[j]1) = p r i m e [ j ] − 1 prime[j] - 1 prime[j]1 之后就多了这一项

1.2.1 模板

打印longlong 型是用 printf("%lld", res) 呜呜

时间复杂度是O(n)

int primes[N], cnt;     // primes[]存储所有素数
int euler[N];           // 存储每个数的欧拉函数
bool st[N];         // st[x]存储x是否被筛掉


void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

1.2.2 例题

AcWing 874. 筛法求欧拉函数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e6 + 10;

int primes[N], cnt = 0;
int euler[N];
bool st[N];

void get_euler(int x)
{
    euler[1] = 1;// 第一个数为计入其中提前初始化
    for (int i = 2; i <= x; i ++)
    {
        if (!st[i]){// 当i不是质数时
            primes[cnt ++] = i;
            euler[i] = i - 1;// 质数与它前面的都互质
        }
        for (int j = 0; primes[j] <= x / i; j ++)
        {
            int t = primes[j] * i;

            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main()
{
    int n;
    cin >> n;

    LL res = 0;

    get_euler(n);

    for (int i = 1; i <= n; i ++) res += euler[i];
    printf("%lld", res);
    return 0;
}

2 快速幂

2.1 直接求快速幂

快速幂就是快速求出: a k a^{k} ak mod p 的值

  • 当幂是偶数时将内部数平方再计算
  • 当幂是奇数是将这个数提出来在按偶数计算
    这样就将O(n) 优化成O(logn)
    下面思路不予考虑 下面思路不予考虑 下面思路不予考虑

时间复杂度是O( l o g k logk logk)
1 <= a, p, k <= 1 0 9 10^9 109

思路是反复平方法
首先预处理出: a 2 0 a^{2^{0}} a20 mod p, a 2 1 a^{2^{1}} a21 mod p, a 2 2 a^{2^{2}} a22 mod p, …… a 2 l o g k a^{2^{logk}} a2logk mod p

a^k
= a 2 0 a^{2^{0}} a20 mod p * a 2 1 a^{2^{1}} a21 mod p * a 2 2 a^{2^{2}} a22 mod p * …… * a 2 l o g k a^{2^{logk}} a2logk mod p

= a 2 x 1 + 2 x 2 + … … 2 x t a^{2^{x1} + 2 ^ {x2} + …… 2 ^ {xt}} a2x1+2x2+……2xt

将k换成二进制即可 k -> 2的次方之和
每一个数都是上一个数的平方模p


2.1.1 模板

时间复杂度是O( l o g k logk logk)

求 t^k mod p,时间复杂度 O(logk)。

int qmi(int t, int k, int p)
{
    int res = 1 % p;
    while (k)
    {
        if (k&1) res = res * t % p;// b%2==1 => k&1 当幂是奇数是将提出一个数在按偶数形式计算
        t = t * t % p;// 偶数平方在计算
        k >>= 1;// 幂除以2
    }
    return res;
}

2.1.2 例题

AcWing 875. 快速幂

// 通过将a^k % p 普通要将a 循环n次相乘 O(n)的复杂度
// 但是当我们将a 进行二进制表示之后求出二进制中为1的项,而将这些项相乘(因为幂运算幂是相加的)就是结果log(n)的复杂度
// 将a二进制预处理的方法是后面一项是前面一项的平方mod p

#include <iostream>
using namespace std;


typedef long long LL;

int qmi(int a, int k, int p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res*a % p;
        a = a * (LL)a % p;
        k >>= 1;
    }
    return res;
}
int main ()
{
    int n;
    cin >> n;
    while (n --)
    {
        int a, k, p;
        scanf ("%d%d%d", &a, &k, &p);

        printf("%d\n", qmi(a, k, p));
    }
    return 0;
}

2.2 利用快速幂求逆元


逆元就是将除法转换成乘法,以上就称x是b mod p的逆元
给定的p是质数的话就是联想到了费马小定理了
如何找到b的逆元呢?

  • 只需要找到 b * x ≡ \equiv 1 (mod p)
  • 这样满足上式的x根据费马小定理就可以的到x = b p − 2 b ^ {p - 2} bp2
  • 当p是b的倍数是无解这是余数为0不可能为1
  • 当p不是质数时就需要使用扩展欧几里得算法

2.2.1 例题

AcWing 876. 快速幂求逆元

/*
    只有当q为质数时才能用这个方法求逆元(费马小定理)一般情况下用扩展欧几里得算法
*/
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int t, int k, int q)
{
    LL res = 1 % q;
    while (k)
    {
        if (k & 1) res = res * t % q;
        t = (LL) t * t % q;
        k >>= 1;
    }
    return res;
}

int main()
{
    int n;
    cin >> n;

    while (n -- ){
        int t, q;

        scanf("%d%d", &t, &q);

        if (t % q == 0) puts("impossible");// 存在乘法逆元的充分条件是t 与 q 互质所以要判断
        else printf("%lld\n", qmi(t, q - 2, q));
    }
    return 0;
}

3 扩展欧几里得算法

裴蜀定理:有一对正整数a, b, 那么一定存在非零整数x, y, 使得ax + by = (a, b)(最大公约数)

  • 边界情况是a和0的最大公因数是a,此时x = 1,y = 0


a % b = a - (a / b) * b

带入就会有(其中d是a和b的最大公约数)
exgcd(b, a % b, x, y) 参数的位置与传入的位置对应
b ∗ x 1 + ( a − ( a / b ) ∗ b ) ∗ y 1 b*x_1 + (a-(a/b)*b)*y_1 bx1+(a(a/b)b)y1

= b ∗ x 1 + a ∗ y 1 – ( a / b ) ∗ b ∗ y 1 b*x_1 + a*y1 – (a/b)*b*y_1 bx1+ay1–(a/b)by1

= a ∗ y 1 + b ∗ ( x 1 – a / b ∗ y 1 ) = g c d 发现 x = y 1 , y = x 1 – a / b ∗ y 1 a*y_1 + b*(x_1 – a/b*y_1) = gcd 发现 x = y_1 , y = x_1 – a/b*y_1 ay1+b(x1a/by1)=gcd发现x=y1,y=x1a/by1
当x, y传参数是互换就成了
x = x 1 , y = y 1 − a / b ∗ x 1 x = x_1 , y = y_1 - a / b * x_1 x=x1,y=y1a/bx1
这样就可以直接传递x, y对应的不需要交换exgcd(b, a % b, y, x)// 记忆:x不变传递顺序的y - a / b * x
x, y是根据与a, b相连的系数看出来的

递归之后就会有a的系数不变,b的系数变成上述
当递归到最后一层(a, b) = (b, r) 最后一层就是b和r的情况下的x,y返回到(a, b)的系数

3.1 模板

// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}

3.2 例题

AcWing 877. 扩展欧几里得算法

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int exgcd(int a, int b, int &x, int &y)
{
    if (!b){// 边界条件 b为零那么a与0的最大公因数是a
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);// 递归后后y的值就变了后面给它改回来
    y = y -  a / b * x;
    return d;
}

int main()
{
    int n;
    cin >> n;
    while (n --)
    {
        int a, b, x, y;
        scanf("%d%d", &a, &b);

        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

4 中国剩余定理


前提条件: m 1 , m 2 … … m n 两两互质 m_1, m_2……m_n两两互质 m1,m2……mn两两互质

证明:
在这里插入图片描述
构造x的基础解系 与当前模m_1的那个为1,其他模m_i的为0就是 a 1 ∗ M 1 ∗ M 1 − 1 a1*M_1*M_1^{-1} a1M1M11
其中 M 1 中不含 m 1 M_1中不含m_1 M1中不含m1所以x模就是0,同时利用 M 1 − 1 M_1^{-1} M11来使模这个 m 1 m_1 m1等于表达式的值 a 1 a_1 a1
这样就得到了通解

5 参考文献

数学知识(二)
快速幂算法求解
中国剩余定理
整数表达式的奇怪方式题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
中国剩余定理(Chinese Remainder Theorem)是一种求解同余方程组的方法,可以用于解决一些数学和计算机科学问题。 下面是使用 C++ 实现中国剩余定理的示例代码: ```c++ #include <iostream> using namespace std; // 求 a 和 b 的最大公约数 int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } // 求 a 和 b 的最小公倍数 int lcm(int a, int b) { return a * b / gcd(a, b); } // 求逆元 int inv(int a, int m) { int x, y; int d = gcd(a, m); if (d == 1) { // 如果 a 和 m 互质,则存在逆元 x = 1; y = 0; while (a > 0) { int q = m / a; int t = m % a; m = a; a = t; int tmp = x - q * y; x = y; y = tmp; } return x < 0 ? x + m : x; } else { // 如果 a 和 m 不互质,则不存在逆元 return -1; } } // 中国剩余定理 int CRT(int a[], int m[], int n) { int M = 1; for (int i = 0; i < n; i++) { M = lcm(M, m[i]); // 求所有模数的最小公倍数 } int x = 0; for (int i = 0; i < n; i++) { int Mi = M / m[i]; int ti = inv(Mi, m[i]); x = (x + a[i] * Mi * ti) % M; } return x; } int main() { int a[] = {2, 3, 2}; int m[] = {3, 5, 7}; int n = 3; int x = CRT(a, m, n); // 求解同余方程组 cout << "x = " << x << endl; return 0; } ``` 在上面的代码中,`CRT()` 函数接收三个参数:`a` 数组表示模方程组中的余数,`m` 数组表示模数,`n` 表示模方程组的个数。函数返回值是模方程组的解。 需要注意的是,该代码中求解逆元的方法是使用扩展欧几里得算法。如果模数很大,可以使用欧拉定理和扩展欧拉定理来求解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ˇasushiro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值