算法基础4.2欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理

欧拉函数

欧拉函数的定义

1∼N中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,N=p1a1p2a2…pmam,则:
ϕ(N)= N×p1−1/p1×p2−1/p2×…×pm−1/pm

第(3)步后还要继续处理四个数相乘的倍数,然后再处理五个数,以此类推。
最后就可以发现欧拉函数的公式展开后和上面用容斥原理求出的式子是相同的

若N=p1α1p2α2…pkαk,则有ϕ(N)=N(1−1/p1)(1−1/p2)…(1−1/pk)。

如:N=6=2∗3,则ϕ(N)=6∗(1−1/2)(1−1/3)=2。

证明:容斥原理

要计算1−N中与N互质的数的个数:从1−N中去掉p1,p2,…,pk的倍数加上所有pi∗pj的倍数

去掉所有pi∗pj∗pk的倍数......
则有N−N/p1−N/p2−…−N/pk+N/p1p2+N/p1p3+…−N/p1p2p3−N/p2p3p4+N/p1p2p3p4+……,展开后即为上式。

作者:失去理智
链接:https://www.acwing.com/file_system/file/content/whole/index/content/9240274/
来源:AcWing

例题:

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

输入格式

第一行包含整数 n。

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

输出格式

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

数据范围

1≤n≤100,
1≤ai≤2×10的9次方

输入样例:
3
3
6
8
输出样例:
2
2
4

核心: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;
}

#include <iostream>
using namespace std;
int main(){
    int n; //需要判断的数的个数
    scanf("%d",&n);
    while(n --){ //n个数
        int a;
        long long res; //记得long long
        scanf("%d",&a);
        res = a; //把res赋值为这个数
        for(int i = 2;i <= a / i;i ++){ //找质因数
            if(a % i == 0){ //如果这个数是可以整除a的
                res = res * (i - 1) / i;
                //这个式子是转换过来的
                //原式为:
                //res * (1 - 1 / i)
                //因为1 / i有可能为小数,而/是整除的意思,和答案可能不符
                //所以我们在后面*i再/i就可以化简成这样啦
                //详细化简过程见解析①
                while(a % i == 0) a /= i; //把a除到a的最小质因数
            }
        }
        if(a > 1) res = res * (a - 1) / a; //如果还大于1,说明还有一个质因子为a
        printf("%lld\n",res); //输出答案
    }
    return 0;
}

筛法求欧拉函数

例题:筛法求欧拉函数

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

输入格式

共一行,包含一个整数 n。

输出格式

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

数据范围

1≤n≤10的6次方

输入样例:
6
输出样例:
12

核心: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);
        }
    }
}


作者:yxc
链接:https://www.acwing.com/blog/content/406/
来源:AcWing

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1000010;

int primes[N], cnt;
int phi[N];
bool st[N];

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

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

    get_eulers(n);

    LL res = 0;
    for (int i = 1; i <= n; i++) res += phi[i];
    printf("%lld\n", res);

    return 0;
}
 

代码解释:
质数i的欧拉函数即为phi[i] = i - 1:1 ~ i−1均与i互质,共i−1个。
phi[primes[j] * i]分为两种情况:
① i % primes[j] == 0时:primes[j]是i的最小质因子,也是primes[j] * i的最小质因子,因此1 - 1 / primes[j]这一项在phi[i]中计算过了,只需将基数N修正为primes[j]倍,最终结果为phi[i] *primes[j]。
② i % primes[j] != 0:primes[j]不是i的质因子,只是primes[j] * i的最小质因子,因此不仅需要将基数N修正为primes[j]倍,还需要补上1 - 1 / primes[j]这一项,因此最终结果phi[i] * (primes[j] - 1)。

作者:番茄酱
链接:https://www.acwing.com/solution/content/3952/
来源:AcWing

欧拉定理:

快速幂

(快速幂) O(nlogk)

快速幂其实就是一种对暴力算法进行的优化,而不是一种全新的算法。
我们发现一次一次的平方太慢了,那我们就可以在平方的基础上再平方,那么,这样的时间复杂度就会得到优化。
那么原来的暴力做法时间复杂度为O(nk),那么我们现在就可以把其中的k优化到log级别,成为O(nlogk),这种方法叫做反复平方法,也是快速幂的核心方法。

例题:

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

输入格式

第一行包含整数 n。

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

输出格式

对于每组数据,输出一个结果,表示 ai的bi次方modp的值。

每个结果占一行。

数据范围

1≤n≤100000,
1≤ai,bi,pi≤2×10的9次方

输入样例:
2
3 2 5
4 3 9
输出样例:
4
1

核心:求 m^k mod p,时间复杂度 O(logk)。

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

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
long long qmi(int a,int k,int p){
    int res = 1; // 快速幂答案
    while(k){ // 直到k为0
        if(k & 1) res = (long long) res * a % p; // 如果k的末尾为1的话,计算
        k >>= 1; // 把k的末尾删掉,因为k的末尾已经计算完了
        a = (long long) a * a % p; // 把a变成下一个数
    }
    return res;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++){
        int a,k,p;
        scanf("%d%d%d",&a,&k,&p);
        printf("%lld\n",qmi(a,k,p));
    }
    return 0;
}

快速幂求逆元

乘法逆元的定义

若整数 b,m互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(modm),则称 x 为 b 的模 m 乘法逆元,记为 b的-1次方(modm)。

b 存在乘法逆元的充要条件是 b与模数 m 互质。当模数 m 为质数时,b的m−2次方 即为 b 的乘法逆元。

例题:

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

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

输入格式

第一行包含整数 n。

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

输出格式

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

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

数据范围

1≤n≤10的5次方,
1≤ai,pi≤2∗10的9次方

输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible

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

int main()
{
    int n; cin >> n;
    while(n --){
        int a, p;
        cin >> a >> p;
        if(a % p == 0) puts("impossible");
        else cout << qmi(a, p - 2, p) << endl;
    }
    return 0;
}

作者:Hz
链接:https://www.acwing.com/solution/content/3054/
来源:AcWing

扩展欧几里得算法

例题:

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int &x, int &y){//返回gcd(a,b) 并求出解(引用带回)
    if(b==0){
        x = 1, y = 0;
        return a;
    }
    int x1,y1,gcd;
    gcd = exgcd(b, a%b, x1, y1);
    x = y1, y = x1 - a/b*y1;
    return gcd; 
}
int main(){
    int n,a,b,x,y;
    cin>>n;
    while(n--){
        cin>>a>>b;
        exgcd(a,b,x,y);
        cout<<x<<" "<<y<<endl;
    }
    return 0;
}

例题:线性同余方程

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
int N,M;

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

int main(){
    int i,j,k;

    cin>>N;
    for(i = 1;i<=N;i++){
        int a,b,m;
        scanf("%d %d %d",&a,&b,&m);
        int x,y;
        int d = exgcd(a,m,x,y);
        if(b%d==0){
            int t = b/d;
            printf("%d\n",((long long)x*t%(m/d)+(m/d))%(m/d));
        }
        else
            printf("impossible\n");
    }
    return 0;
}


作者:青衫白衣
链接:https://www.acwing.com/solution/content/11231/
来源:AcWing

中国剩余定理

例题:表达整数的奇怪方式

#include<iostream>

using namespace std;

typedef long long LL;//数据范围比较大,所以用LL来存储

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

int main()
{
    int n;
    LL a1,m1;
    cin>>n>>a1>>m1;
    LL x=0;
    for(int i=1;i<n;i++)
    {
        LL a2,m2;
        cin>>a2>>m2;
        LL k1,k2;
        LL d=exgcd(a1,a2,k1,k2);
        if((m2-m1)%d)
        {
            x=-1;
            break;
        }
        k1*=(m2-m1)/d;
        //因为此时k1是k1*a1+k2*a2=d的解,所以要乘上(m2-m1)/d的倍数大小
        LL t=abs(a2/d);
        k1=(k1%t+t)%t;
        //数据比较极端,所以只求k的最小正整数解
        m1=k1*a1+m1;
        //m1在被赋值之后的值为当前"x"的值,此时赋值是为了方便下一轮的继续使用
        a1=abs(a1*a2/d);
        //循环结束时a1的值为当前所有的a1,a2,……an中的最小公倍数
    }
    if(x!=-1)
    x=(m1%a1+a1)%a1;
    //当循环结束时,此时的值应该与最小公倍数取模,以求得最小正整数解
    printf("%lld\n",x);
    return 0;
}

end

_____________________________________________________________________________

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值