算法基础课—数学知识(二)欧拉函数、欧拉定理、费马定理、快速幂、扩展欧几里得算法、逆元

欧拉函数

定义:

在这里插入图片描述
互质是公约数只有1的两个整数,叫做互质整数。

公式计算

在这里插入图片描述

定理的证明——用容斥原理算出来

在这里插入图片描述

思路:1-N中与N互质的个数——先对N进行分解质因数,可以用筛选法,去掉其所有质因数的倍数,剩下的就是互质的数
对于筛法——容斥原理(先筛去所有素数的倍数,同时会发现可能两个素数倍数相同,却被筛去了,所以要诚信加回来,于是就有了公式的第二行。同时又发现,如果做加法的话,中间三个都有的交叉区域会被多次叠加,所以要减去他们多加的重叠区域,然后以此类推,可以得到如上的公式,又会发现其是总公式的展开形式,于是证明了定理)。

题目

给定 n 个正整数 ai,请你求出每个数的欧拉函数。

欧拉函数的定义
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,N=pa11pa22…pamm,则:
ϕ(N) = N×p1−1p1×p2−1p2×…×pm−1pm
输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式
输出共 n 行,每行输出一个正整数 ai 的欧拉函数。

数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
3
6
8
输出样例:
2
2
4

求欧拉函数——模板

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

求欧拉函数——自己的代码

#include<iostream>
using namespace std;
int main(){
    int n, i, j, x;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>x;
        long long res = x;//可能res较大,所以要用long long
        for(j = 2; j <= x / j; j ++){
            if(x % j == 0){
                res *= 1 - 1.0 / j;//注意要是1.0/j,否则得到的不会是分数
                while(x % j == 0) x = x / j;
            }
        }
        if(x > 1) 
            res = res * (1 - 1.0 / x);
        cout<<res<<endl;
    }
}

线性筛法求每个数的欧拉函数

关键:抓住pji和i之间的欧拉函数的关系。如果是i是素数,则其欧拉函数为i-1.
我们之前用线性筛法用线性的时间就可以把所有不是素数的数筛除,在这个基础上,我们也可以把每个数的欧拉函数顺便求一遍。
我们可以发现欧拉函数就是要求的其实是他们的分解质因数,不需要知道他们的幂次,而在线性筛法中,我们是z抓住了pj
i和i之间的关系,然后对他们的pji的数进行操作,于是我们是否也可以推导φ(pji)和φ(i)的关系呢?于是我们就有了下面的情况:

在这里插入图片描述
第一种情况i mod pj == 0, 说明pj是i的最小质因数,于是φ(pj * i)= pj * φ (i)
第二种情况 i mod pj != 0 , 说明pj不是i的质因数,于是φ(pj * i)= pj * φ(i)*(1-1/pj)
于是我们便可以用线性筛法来求解每个数的欧拉函数

题目

给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

输入格式
共一行,包含一个整数 n。

输出格式
共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。

数据范围
1≤n≤106
输入样例:
6
输出样例:
12

筛法求欧拉函数——模板

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

筛法求欧拉函数——自己的代码

#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int primes[N],cnt = 0;
long long euler[N];
bool st[N];
int main(){
    int n, j, i;
    cin>>n;
    long long res = 1;//注意i==1的时候其欧拉函数==1
    for(i = 2; i <= n; i ++){
        if(!st[i]){
            primes[cnt ++] = i;
            euler[i] = i - 1;//注意定义如果是素数的情况
        }
        for(j = 0; primes[j] <= n / i; j ++){
            st[primes[j] * i] = true;
            if(i % primes[j] == 0){
                euler[primes[j] * i] = euler[i] * primes[j];
                break;
            }
            else{
               euler[primes[j] * i] = euler[i] * primes[j] * (1 - 1.0 / primes[j]);
            }
        }
        res += euler[i];
    }
    cout<<res<<endl;
    
}

欧拉函数的应用——欧拉定理

如果a和n互质,则a的φ(n)次方 mod n 同余1
在这里插入图片描述
证明
若a与n互质,
假设在1-n中有a1…aφ(n)的互质数,将他们相乘得到M = a1…aφ(n)
如果将这φ(n)个互质数都分别乘以a,再同时相乘得到M,会发现他们mod n的结果都相同,于是
得到第三行公式,于是推出第四行公式
在这里插入图片描述

欧拉定理的特例——费马定理

当n是质数p时
φ(p) = p - 1
所以a的p-1次方mod p 等于1
在这里插入图片描述

快速幂

快速的求出a的k次方mod p 的结果
时间复杂度logk
在这里插入图片描述
原先计算a的k次方需要k次操作,我们发现可以把k化成logk个数相加,不禁联想到二进制的形式
a的k次方 可以等于 a 的 2的x1次方+a的2的x2次方
2^x1 + 2 ^ x2 +…+ 2 ^xt = k (二进制)
于是我们只需要分别去计算a的2 ^ xk 次方是多少,并依次把他们加起来
同时a的2 ^ xk = (a的2 ^ (xk - 1))^ 2,所以也方便叠加求解

快速幂的思想,相当于以每次2的幂次的速度到达a的k次方。会比不断叠加1要来的快
在这里插入图片描述

题目

给定 n 组 ai,bi,pi,对于每组数据,求出 abiimodpi 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含三个整数 ai,bi,pi。

输出格式
对于每组数据,输出一个结果,表示 abiimodpi 的值。

每个结果占一行。

数据范围
1≤n≤100000,
1≤ai,bi,pi≤2×109
输入样例:
2
3 2 5
4 3 9
输出样例:
4
1

模板

#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;
        a = a * (LL)a % p;
        b >>= 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;
}


自己的代码

#include <iostream>
using namespace std;
typedef long long LL;
int main(){
    int b, n, p;
    int i;
    LL a;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>a>>b>>p;
        LL res = 1 % p;
        a = a % p;
        while(b){
            if(b & 1)//取最后一位
                res = res * a % p;
            a = a * a % p;//这个要写在if判断外面,不断是否有都要继续乘
            b = b >> 1;//注意不能只写b>>1 还要赋值
        }
        cout<<res<<endl;
    }
}

快速幂求逆元

关键:只有当b和x互质,x为质数的时候才可以用。
把复杂的除法转变成乘法,x就是b的逆元
在这里插入图片描述
可以发现其本身与逆元之间的关系是
b * b的逆元 在mod n 会等于 1
在这里插入图片描述
可以发现b乘b的逆元mod p等于1
在这里插入图片描述

于是我们联想到费马定理(p一定是质数)

所以b的p-2次方就是b的逆元,于是可以用快速幂来求解
在这里插入图片描述
同时要注意,只有b和p互质且p是质数,才可以用费马定理,才可以以此求出逆元

题目

给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。

注意:请返回在 0∼p−1 之间的逆元。

乘法逆元的定义
若整数 b,m 互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2 即为 b 的乘法逆元。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个数组 ai,pi,数据保证 pi 是质数。

输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。

若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。

数据范围
1≤n≤105,
1≤ai,pi≤2∗109
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible

模板

#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");
        else printf("%lld\n", qmi(a, p - 2, p));
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/50238/
来源:AcWing

自己的代码

#include<iostream>
using namespace std;
typedef long long LL;
int main(){
    int n, i, b, p;
    cin>>n;
    for(i = 0; i < n; i++){
        cin>>b>>p;
        if(b % p == 0) cout<<"impossible"<<endl;
        else{
            LL a = b % p, res = 1;
            int t = p - 2;
            while(t){
                if(t & 1) res = res * a % p;
                a = a * a % p;
                t = t >> 1;
            }
            cout<<res<<endl;
        }
    }
    
}

扩展欧几里得算法

裴蜀定理

对于一个正整数a,b ,一定存在整数x,y,使得ax + by = a和b的最大公约数,即a和b能凑出来的最小的正整数
在这里插入图片描述

证明

a是最大公约数的倍数,b是最大公约数的倍数,所以ax+by也是最大公约数的倍数,所以d是最大公约数的倍数,所以一定存在x,y ,ax+by= 最大公约数,其是ax+by能凑出的最小正整数
在这里插入图片描述

求解过程

在这里插入图片描述

算法本质上是要去求x0和y0
ax+by = (a,b),又(a,b) = (b, a mod b) ,
于是根据(b,a mod b)又可以推导出 by + (a mod b)x = (b,a mod b)。
进一步推导等于by + (a - [a/b] * b)*x =  (b,a mod b)。
整理一下会等于ax + b*( y - [a/b] * x) = d。于是可以更新上一步的x和y。
相当于在求最大公约数的过程中,a,b一直在往下走,
所以x,y一直也在往下走,知道a % b == 0 ,则返回最大公约数a,
然后反过来迭代回来更新x和y,直到更新到最后一步的时候,才是真正的x,y。

主要核心思想:(a,b) = (b, a mod b)。可以一直这么递归下去,知道a mod b = 0 ,则返回a,同时这个时候x,y也是确定的。
又利用 (a,b)= ax + by 然后进一步递归到 b,a mod b 时 x,y 发生了更新,于是反过来更新x,y。

在这里插入图片描述

求出一个解可以求出多个通解,可以发现可能存在多个x,y
在这里插入图片描述
在这里插入图片描述

题目

给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 ai×xi+bi×yi=gcd(ai,bi)。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含两个整数 ai,bi。

输出格式
输出共 n 行,对于每组 ai,bi,求出一组满足条件的 xi,yi,每组结果占一行。

本题答案不唯一,输出任意满足条件的 xi,yi 均可。

数据范围
1≤n≤105,
1≤ai,bi≤2×109
输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1

模板

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

代码

#include <iostream>
using namespace std;
int gcb(int a, int b, int &x, int &y){
    if(!b){
        x = 1;
        y = 0;
        return a;
    }
    else{
        int s = gcb(b, a % b, y, x);
        y = y - a/b * x;
        return s;
    }
}
int main(){
    int n, x, y, i, a, b;
    cin>>n;
    for(i = 0; i < n; i++){
        cin>>a>>b;
        gcb(a, b, x, y);
        cout<<x<<" "<<y<<endl;
    }
}

线性同余方程

a和x的乘积除以m的余数是b,要求这个x

题目的等价变形
在这里插入图片描述
如果b是a,m最大公约数的倍数,则有解,否则,无解
如果有解,则按照扩展欧几里得算法来求即可。

中国剩余定理

****这一块留待补充
在这里插入图片描述

题目

给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi),如果无解则输出 impossible。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组数据 ai,bi,mi。

输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi,如果无解则输出 impossible。

每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。

输出答案必须在 int 范围之内。

数据范围
1≤n≤105,
1≤ai,bi,mi≤2×109
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3

模板

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

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/50248/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码

#include <iostream>
using namespace std;
int gcb(int a, int b, int &x, int &y){
    if(!b){
        x = 1;
        y = 0;
        return a;
    }
    else{
        int s = gcb(b, a % b, y, x);
        y = y - a / b * x;
        return s;
    }
}
int main(){
    int i, n, a, b, m, x, y;
    cin>>n;
    for(i = 0; i < n; i ++){
        cin>>a>>b>>m;
        int s = gcb(a, m, x, y);
        if(b % s == 0) cout<<x<<endl;
        else cout<<"impossible"<<endl;
    }
}

逆元

这一段部分摘自https://blog.csdn.net/wcxyky/article/details/103366577

前提

满足求余的运算

(a +  b) % p = (a%p +  b%p) %p  (对)

(a  -  b) % p = (a%p  -  b%p) %p  (对)

(a  *  b) % p = (a%p *  b%p) %p  (对)

(a  /  b) % p = (a%p  /  b%p) %p  (错)

除法不满足求余取模运算

逆元的定义

a*x = 1 (mod p) 其中x叫做a关于p的逆元

比如2 * 3 % 5 = 1,(即2*3=1(mod 5))那么3就是2关于5的逆元,或者说2和3关于5互为逆元

a的逆元,我们用inv(a)来表示

那么(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p

逆元:

a和p互质,a才有关于p的逆元

求逆元方法1——快速幂、费马定理——前提:其关于的数是质数p,且与a互质

求逆元方法1:

由费马小定理 a^(p-1) ≡1 (mod p)

两边同除以a得:a^(p-2) ≡ inv(a) (mod p)

所以inv(a) = a^(p-2) (mod p)

这个用快速幂可以求,复杂度O(logn)

求逆元方法2——扩展欧几里得定理

求逆元
a * x = 1 (mod m)
方法:相当于这里的b 为1,去求x ,实现方法和扩展欧几里得类似。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值