数论 - 欧拉函数、快速幂、扩展欧几里得算法

前言

本章博客将介绍欧拉函数、快速幂、扩展欧几里得算法的常用模板,有些思路是基于上一篇博客《数论 - 质数和约数》,在文中已经标注出来,如果难以理解,可以先查看上一篇博客。

欧拉函数部分将介绍:求解欧拉函数、筛法求欧拉函数
快速幂部分将介绍:求解快速幂、快速幂求逆元
扩展欧几里得算法部分将介绍:扩展欧几里得算法求裴蜀定理系数、线性同余方程

一、欧拉函数

欧拉函数ϕ(N)表示1~n中与n互质的数的个数

1.求解欧拉函数

  • 题目:给定 n 个正整数 ai,请你求出每个数的欧拉函数。
  • 分解质因数:N = P1a1 * P2a2 * P3a3 * …… * Pkak
  • 欧拉函数:ϕ(N)=N(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk)
  • 要运用分解质因子的模板(在上一篇博客《数论-质数与约数》中提到)
#include <iostream>

using namespace std;

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;
}

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        cout << phi(x) << endl;
    }

    return 0;
}

2.筛法求欧拉函数

  • 题目:给定一个正整数 n ,求 1∼n 中每个数的欧拉函数之和。
  • 基于线性筛法(在上一篇博客《数论-质数与约数》中提到)求素数,基于线性筛法的三种情况进行扩展,求欧拉函数
  • 如果一个数p是质数,那么ϕ( p ) = p-1,因为比p小的所有数都和p互质
  • 如果i mod pj == 0(pj是i的最小质因子),则ϕ(i)=i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),而ϕ(pj*i)=pj*i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),所以ϕ(pj*i)=pjϕ(i)
  • 如果i mod pj != 0(pj只是pj*i的最小质因子),则ϕ(i)=i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),而ϕ(pj*i)=pj*i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk)(1-1/pj),所以ϕ(pj*i)=pjϕ(i)(1-1/pj)
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1000010;

int primes[N], cnt;
int euler[N]; //每个数的欧拉函数
bool st[N];

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);
        }
    }
}

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

    get_eulers(n);
    LL res = 0;
    for (int i = 1; i <= n; i ++ ) res += euler[i];

    cout << res << endl;

    return 0;
}

二、快速幂

通过预处理和反复平方法,在时间复杂度为O (logN)下快速求出某个数的某次幂,暴力时间复杂度为O (N),快了很多

1.求解快速幂

  • 题目:给定 n 组 ai,bi,pi,对于每组数据,求出 a的b次方 mod p 的值。
  • 求ak mod p
  • 预处理算出:a的20次方~a的2logk每个数mod p的值
  • 选取部分预处理数相乘(相当于可以拼接出ak)mod p即可得到答案,选取步骤通过k的二进制数来确定,取1则选取,取0则不取
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p)
{
    LL res = 1 % p;
    while (b)
    {
        if (b & 1) res = res * a % p; //该位是1则选取该预处理结果相乘
        a = a * (LL)a % p; //a是不断变化的每一位预处理结果
        b >>= 1; //右移1位
    }
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }

    return 0;
}

2.快速幂求逆元

  • 题目:给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible
  • 若整数 b,m互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a*x(mod m)),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)。
  • b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2 即为 b 的乘法逆元。
  • 逆元作用:我们必须在中间过程中进行求余,否则数字太大,但是(a / b) % p != (a%p / b%p) %p ,所以我们要将除法转化为乘法,来满足求余时的“分配律”
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, p;
        scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible"); //由于p是质数,所以a和p不互质是a一定是p的倍数
        else printf("%lld\n", qmi(a, p - 2, p));
    }

    return 0;
}

三、扩展欧几里得算法

欧几里得算法即上一篇博客《数论-质数与约数》中求最大公约数的辗转相除法,即(a,b) = (b,a mod b),扩展欧几里得算法是在此基础上的扩展

1.扩展欧几里得算法求裴蜀定理系数

  • 题目:给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 aixi+biyi=gcd(ai,bi)。
  • 裴蜀定理:对于任意正整数a、b,一定存在整数x、y(x,y不同时为0),使得ax + by = (a,b),(a,b)表示a和b的最大公约数
  • 如果b == 0,则x = 1,y = 0
  • 如果 b != 0,则最大公约数d = exgcd(b, a % b, y, x),x和y颠倒仅仅是为了方便计算,此时 by + (a mod b)x = d,则ax + b(y - (a/b)下取整x) = d,可以看出颠倒xy后化简出的式子依旧为ax,所以x是不用更改的,更加方便计算,而y = y - (a/b)下取整x
  • 用欧几里得算法递归求取,xy用引用求取
#include <iostream>
#include <algorithm>

using namespace std;

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;
}

int main()
{
    int n;
    scanf("%d", &n);

    while (n -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int x, y;
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }

    return 0;
}

2.线性同余方程

  • 题目:给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai*xi≡bi(mod mi),如果无解则输出 impossible
  • ax≡b(mod m)等价于ax = my + b。等价于令y = -y,ax + my = b,即转化为为扩展欧几里得算法求裴蜀定理系数(上一题)
  • 则b如果是(a,m)的倍数就一定成立,满足裴蜀定理,相当于把等式ax + my = b左右同时乘以b/(a,m)
  • 则b如果不是(a,m)的倍数,一定不成立
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

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;
}

int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, b, m;
        scanf("%d%d%d", &a, &b, &m);

        int x, y;
        int d = exgcd(a, m, x, y);
        if (b % d) puts("impossible");
        else printf("%d\n", (LL)b / d * x % m);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值