数学知识2

本节内容:

1.欧拉函数

2.快速幂

3.扩展欧几里得算法

4.中国剩余定理

1.欧拉函数:

<i>求一个数的欧拉函数:

时间复杂度:O(sqrt(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);  //  公式里1 - 1/p1 转化成 (p1 - 1) / p1;
        }
        
        while (x % i == 0) x /= i;
    }
    
    if (x > 1) res = res / x * (x - 1);
    return res;
}

<ii> 求1到n中每个数的欧拉函数之和(线性筛求欧拉函数):

时间复杂度: O(n)

思路:

我们可以用线性筛法把数字分成质数和合数

质数的欧拉函数就是phi[i] = i - 1; 

合数时,有两种情况

第一种情况时,pj是i的最小质因子,pj小于i的所有质因子,所以pj同时也是i * pj的最小质因子,就有了上面的式子,而 i * (1 - 1/pj) ... (1 - 1/pk)正好是i的欧拉函数,所以简化成phi[i] * pj

第二种情况时,pj 不是i的质因子,又因为 pj * i 的质因子有 pj 和 i 的所有质因子,所以就是第二个式子,而同理把 i * (1 - 1/p1) ... (1 - 1/pk) 简化成phi[i]

代码:

int get_eulers(int n)
{
    euler[1] = 1;  //  初始化
    for (int i = 2; i <= n; i ++ )  // 线筛模版
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;   //从1到质数i中与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);
        }
    }
    
     LL res = 0;
    for (int i = 1; i <= n; i ++ ) res += euler[i];
    return res;
}

<iii>欧拉定理:

对于任意正整数 a 和 n,如果 a 和 n 互质,则有:a^ϕ(n) ≡ 1 (mod n),就是a的ϕ(n)次方模n为1

证明:

特殊情况时:

2.快速幂:

时间复杂度:O(logk)

快速的求出 a ^ k mod p 的结果,其中 1 <= a, p, k <= 10^9

思路:

代码:

typedef long long LL;

//a^k % p
int qmi(int a, int k, int p)
{
    int res = 1;
    while (k) // 对k进行二进制化
    {
        //  如果k的二进制表示的第零位是1,则乘上当前的a
        if (k & 1) res = (LL)res * a % p;
        k >>= 1; // 去掉最后的一位,右移一位
        a = (LL)a * a % p;  //  更新a,因为去掉一位,a要从a^(2^0),变成a^(2^1),以此类推到a^(2^2),a^(2^logk)
    }
    
    return res;
}

用法:快速幂求逆元:

思路:

p是质数,p是质数,p是质数,不是质数就不行了,费马定理就不能用了

代码:

typedef long long LL;

int get_qmi(int a, int k, int p)
{
    int res = 1;
    while (k)  //  快速幂模版
    {
        if (k & 1) res = (LL)res * a % p;
        
        k >>= 1;
        a = (LL)a * a % p;
    }
    
    if (a % p) cout << res << endl;  // 如果a不是p的倍数,输出逆元,否则就没有
    else cout << "impossible" << endl;
}




// main里面的 get_qmi(a, p - 2, p);  //  求a的p - 2次方

3.扩展欧几里得算法:

思路:

代码:

int exgcd(int a, int b, int &x, int &y) // 记得加引用符号,不然在输出xy时,他的值还是0
{
    if (!b)
    {
        x = 1, y = 0;
        return a;
    }
    
    int x1, y1;
    /* 第一种,比较好理解
    int d = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - a/b * y1; */
    
    // 第二种更简便
    
    int d = exgcd(b, a % b, y, x);
    
    y = y - a/b * x;
    
    return d;
}

如果我们求出了x, y,那我们就求出了所有解,因为d是a和b的公约数,所以b/d,a/d一定是整数,所以a, b的系数一定是整数,并且展开后 ab/d消掉了,等式依旧满足

应用:求解线性同余方程:

思路:

代码:

#include <iostream>

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 = 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");  //  b不是d的倍数,一定无解
        else printf("%d\n", (LL)x * (b/d) % m);  // 否额输出解
    }
    
    return 0;
}

4.中国剩余定理:

这个好难......

思路:

对于一般的定理

例题:

题目:

代码(扩展):
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;  //  数的范围较大用longlong存

LL exgcd(LL a, LL b, LL &x, LL &y)  //  扩展欧几里得算法模版  求ax + by = gcd(a, b);
{
    if (!b)
    {
        x = 1, y = 0;
        return a;
    }
    
    LL d = exgcd(b, a % b, y, x);
    y = y - a / b * x;
    return d;
}


int main()
{
    
    int n;
    cin >> n;
    
    bool has_answer = true;  //  bool存储是否有解
    LL a1, m1;  //  第一个方程的系数
    
    cin >> a1 >> m1;
    
    for (int i = 0; i < n - 1; i ++ )  //  进行n - 1次合并
    {
        LL a2, m2; 
        cin >> a2 >> m2;
        
        LL k1, k2;
        LL d = exgcd(a1, a2, k1, k2);
        if ((m2 - m1) % d)  //  看是否是a1, a2的最大公因数的倍数,是则有解,反之无解
        {
            has_answer = false;
            break;
        }
        
        k1 *= (m2 - m1) / d;  //  因为我们求的是ax + by = d, 而方程是 a1k1 - a2k2 = m2 - m1,所以两边同时乘以(m2 -m1)/d转换成所求方程
        LL t = a2 / d;
        k1 = (k1 % t + t) % t;  //  让其在一个合适的范围,由通解知让其保持在 0 到 a2 /d 之间
        
        m1 = a1 * k1 + m1;  //  合并后的m1就是a1 * k1 + m1
        a1 = a1 / d * a2;  //  合并后的a1就是a1和a2的最小公倍数
        
    }
    
    if (has_answer)
    {
        cout << (m1 % a1 + a1) % a1 << endl;  //  求最小x,就等于 x mod a1
    }
    else puts("-1");
    
    return 0;
}

经典代码(m1,m2......mk 互质时):
int main()
{
    LL res = 0, mul = 1;
    cin >> n;
    for(int i = 0; i < n; i ++)
    {
         cin >> a[i] >> b[i];
         mul *= a[i];
    }
    LL x, y;
    for(int i = 0; i < n; i ++)
    {
        LL Mi = cnt / a[i];

        exgcd(Mi, a[i], x, y);  //  用扩展欧几里得算法求逆元
        if (x < 0) x += m[i];  //  确保 逆元是正数
        
        res += b[i] * Mi % cnt * x % cnt;  // 带入公式
    }
    cout << res % cnt << " ";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值