数论2(欧拉函数+快速幂+扩展欧几里得算法+中国剩余定理)

数论2

写在前面

前面更新了数论1(质数+约束),这次我继续更新数论2(欧拉函数+快速幂)

欧拉函数

欧拉函数

在这里插入图片描述

在这里插入图片描述
下面请看代码:

#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 euler(int a)
{
    int res=a;
    for(int i=2;i<=a/i;i++)
    {
        if(a%i==0)
        {
            while(a%i==0)
                a/=i;
            res = res / i*(i-1);
        }
    }
    if(a>1) res = res /a*(a-1);
    return res;
}
int main()
{
    int n;
    long long res=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        res+=euler(i);
    }
    cout<<res;
    return 0;
}

这样写的时间复杂的为O(n∗√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);
        }
    }
}

最后的总代码:

#include<iostream>

using namespace std;

const int N = 1e6+10;

bool st[N];
int primes[N],idx,euler[N];
int main()
{
    int n;
    euler[1]=1;//与1互质的数就只有1
    long long res = 0;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        if(!st[i])//未被筛过,i为质数
            //对于一个质数n,其euler[n]=n-1,因为若n为质数,那么2~n-1中没有一个数是n的约数,那么1~n中与n互质的数就有1~n-1这n-1个数
            //∴euler[n]=n-1;
            euler[i] = i-1;
            primes[idx++] = i;
        for(int j=0;primes[j]<=n/i;j++)
        {
            st[primes[j]*i] = true;
            /*
            求euler[primes[j]*i],分析euler[primes[j]*i]和euler[i]的关系
            如果i%primes[j]==0时,说明primes[j]为i的最小质因子,而euler[i]内已经乘过(primes[j]-1)/primes[j]
            ∴计算euler[primes[j]*i]只需要更新N的值即可,即在euler[i]的基础上乘个primes[j]
            ∴euler[primes[j]*i]=primes[j]*euler[i];
            如果i%primes[j]!=0时,primes[j]小于i的最小质因子,而euler[i]内没有乘过(primes[j]-1)/primes[j]
            ∴计算euler[primes[j]*i]除了需要更新N值外,还需要乘上(primes[j]-1)/primes[j]
            ∴euler[primes[j]*i]=primes[j]*euler[i]*(primes[j]-1)/primes[j]=euler[i]*(primes[j]-1)
            */
            if(i % primes[j] == 0){
                euler[primes[j]*i]=primes[j]*euler[i];
                break;
            }
            else euler[primes[j]*i]=euler[i]*(primes[j]-1);
        }
    }
    for(int i=1;i<=n;i++) res+=euler[i];
    cout<<res;
    return 0;
}


线性筛法求欧拉数时间复杂度为 O(n)

快速幂

快速幂

在这里插入图片描述

暴力O(n∗b)

基本思路:对于n组数据,分别循环b次求出ab mod p

#include<iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int a,b,p;
        long long res=1;
        cin>>a>>b>>p;
        while(b--)
            res = res * a %p;
        cout<<res<<endl;
    }
}
快速幂解法O(n∗logb)

基本思路:

反复平方法
任何一个数都可以被二进制表示
在这里插入图片描述
b&1就是判断b的二进制表示中第0位上的数是否为1,若为1,b&1=true,反之b&1=false
b&1也可以用来判断奇数和偶数,b&1=true时为奇数,反之b&1=false时为偶数

#include<iostream>

using namespace std;

long long qmi(long long a,int b,int p)
{
    long long res=1;
    while(b)//对b进行二进制化,从低位到高位
    {
        //如果b的二进制表示的第0位为1,则乘上当前的a
        if(b&1) res = res *a %p;
        //b右移一位
        b>>=1;
        //更新a,a依次为a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
        a=a*a%p;
    }
    return res;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        cin.tie(0);
        ios::sync_with_stdio(false);
        int a,b,p;
        long long res=1;
        cin>>a>>b>>p;
        res = qmi(a,b,p);
        cout<<res<<endl;
    }
    return 0;
}

快速幂之递归版 O(n∗logb)
#include<iostream>

using namespace std;

#define ull unsigned long long
ull quick_pow(ull a,ull b,ull p)
{
    if(b==0) return 1;
    a%=p;
    ull res=quick_pow(a,b>>1,p);
    if(b&1) return res*res%p*a%p;
    return res*res%p;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int a,b,p;
        cin.tie(0);
        ios::sync_with_stdio(false);
        cin>>a>>b>>p;
        cout<<quick_pow(a,b,p)<<endl;
    }
    return 0;
}

快速幂求逆元

在这里插入图片描述
在这里插入图片描述

快速幂求逆元

直接把快速幂的代码CV一下就可以了。

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

扩展欧几里得算法求逆元
#include <iostream>
using namespace std;
typedef long long LL;
int n;

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()
{
    cin >> n;
    while (n --)
    {
        int a, p, x, y;
        // if (a < p) swap(a, p);
        cin >>  a >> p;
        int d = exgcd(a, p, x, y);
        if (d == 1) cout << ((LL)x + p) % p << endl;//保证x是正数
        else puts("impossible");

    }
    return 0;
}


扩展欧几里得算法

在这里插入图片描述
拓展欧几里得算法解决的问题:对于任意给定的两个正整数a,b,求解x,y使得ax+by=(a,b) //(a,b)的意思为,a和b的最大公约数

问题的引入:裴蜀定理
给定任意一对正整数a,b,存在非零整数x,y,使得ax+by=(a,b)

在这里插入图片描述
方法一:

#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<cstdio>

using namespace std;

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

    int n;
    scanf("%d",&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;
}

中国剩余定理

表达整数的奇怪方式

在这里插入图片描述
关于这个定理百度百科有很长的一个解释,这里我就不多说了。中国剩余定理(孙子定理)链接
附上代码:

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long 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;
    cin >> n;

    LL x = 0, m1, a1;
    cin >> m1 >> a1;
    for (int i = 0; i < n - 1; i ++ )
    {
        LL m2, a2;
        cin >> m2 >> a2;
        LL k1, k2;
        LL d = exgcd(m1, m2, k1, k2);
        if ((a2 - a1) % d)
        {
            x = -1;
            break;
        }

        k1 *= (a2 - a1) / d;
     
        k1 = (k1 % (m2/d) + m2/d) % (m2/d);

        x = k1 * m1 + a1;

        LL m = abs(m1 / d * m2);
        a1 = k1 * m1 + a1;
        m1 = m;
    }

    if (x != -1) x = (a1 % m1 + m1) % m1;

    cout << x << endl;

    return 0;
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小羊努力变强

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

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

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

打赏作者

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

抵扣说明:

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

余额充值