自学内容网 自学内容网

【算法学习笔记】31:试除法分解质因数及求解欧拉函数

试除法分解质因数

原理

O ( n ) O(\sqrt{n}) O(n )时间内可以对 n n n分解质因数,原理就是从最小质因数2开始遍历,第一个遍历到的因数就是2,一定是一个质因数。

每次都从 n n n里将遇到的质因子除干净,那么下次遇到的因数 i i i也一定是一个质因数。

可以用反证法:如果下次遇到的因数 i i i不是一个质因数,那么说明有 < i <i <i的另一个质因数 j j j,那么因为我们每次遇到质因数都会从 n n n里除干净,所以 j < i j < i j<i一定在过去的某一轮被除干净了,矛盾,证毕。

这个遍历只需要在 i ⋅ i ≤ n i \cdot i \leq n iin的情况下继续就可以了,因为一旦 i ⋅ i > n i \cdot i > n ii>n,最后剩下的要么 n n n是1,要么 n n n是最后一个质因数(且出现了一次方),一定不存在其它没遍历到的质因数了。

可以用反证法:假如 n n n还是一个合数,还存在没遍历到的质因子 i i i,那么 n i \frac{n}{i} in也一定是一个因数,这个因数的任何一个质因子都 < i < i <i,又一定从 n n n里被剔除过了,矛盾,证毕。

最后记得判断一下 n n n是不是剩下一个一次方的最大质因子,也就是 n n n如果不是1就是最后的那个质因数。

例题:AcWing 867. 分解质因数

#include <iostream>

using namespace std;

int main() {
    int t;
    cin >> t;
    
    while (t -- ) {
        int n;
        cin >> n;
        for (int i = 2; i * i <= n; i ++ ) {
            int mi = 0;
            while (n % i == 0) {
                mi ++ ;
                n /= i;
            }
            if (mi) cout << i << " " << mi << endl;
        }
        if (n != 1) cout << n << " " << 1 << endl;
        cout << endl;
    }
    
    return 0;
}

试除法求解欧拉函数

利用试除法分解质因数,同样可以解决欧拉函数的计算问题。

定义与计算公式

欧拉函数 ϕ ( n ) \phi(n) ϕ(n)表示从1到n中和n互质的数字的个数,两个数字除了1没有其它公因数就是互质。比如从1到6之间和6互质的数字只有1和5,所以 p h y ( 6 ) = 2 phy(6) = 2 phy(6)=2

n n n分解质因数之后的结果是:
n = p 1 α 1 + p 2 α 2 + . . + p k α k n = p_1^{\alpha_1} + p_2^{\alpha_2} + .. + p_k^{\alpha_k} n=p1α1+p2α2+..+pkαk
那么欧拉公式的计算公式是:
ϕ ( n ) = n ⋅ ( 1 − 1 p 1 ) ⋅ ( 1 − 1 p 2 ) ⋅ . . . ⋅ ( 1 − 1 p k ) \phi(n) = n \cdot (1 - \frac{1}{p_1}) \cdot (1 - \frac{1}{p_2}) \cdot ... \cdot (1 - \frac{1}{p_k}) ϕ(n)=n(1p11)(1p21)...(1pk1)

证明

思路是用容斥原理。假设 n n n的质因子只有 p 1 p_1 p1 p k p_k pk,那么根据欧拉函数的定义,只要从1到n中去掉 p 1 p_1 p1 p k p_k pk所有数的倍数,最后剩下的就是和 n n n互质的数。

第一步:减去所有 p 1 p_1 p1 p k p_k pk的倍数

n − ⌊ n p 1 ⌋ − ⌊ n p 2 ⌋ − . . . − ⌊ n p k ⌋ n - \lfloor \frac{n}{p_1} \rfloor - \lfloor \frac{n}{p_2} \rfloor - ... - \lfloor \frac{n}{p_k} \rfloor np1np2n...pkn

但是因为某个 p i p_i pi的倍数同时可能也是 p j p_j pj的倍数,所以直接去掉每个数的倍数就会存在多去除的情况,因此就要把多去掉的部分加回来。

第二步:加上所有 p i ⋅ p j p_i \cdot p_j pipj的倍数

+ ⌊ n p 1 ⋅ p 2 ⌋ + ⌊ n p 1 ⋅ p 3 ⌋ + . . . + ⌊ n p k − 1 ⋅ p k ⌋ + \lfloor \frac{n}{p_1 \cdot p_2} \rfloor + \lfloor \frac{n}{p_1 \cdot p_3} \rfloor + ... + \lfloor \frac{n}{p_{k-1} \cdot p_k} \rfloor +p1p2n+p1p3n+...+pk1pkn

进而考虑,如果有一个数是 p i p_i pi p j p_j pj p k p_k pk的倍数,那么在第一步会被 p i p_i pi p j p_j pj p k p_k pk各减去一次,第二步会被 p i ⋅ p j p_i \cdot p_j pipj p j ⋅ p k p_j \cdot p_k pjpk p i ⋅ p k p_i \cdot p_k pipk各加上一次,减掉三次加上三次,没有加没有减,所以下一步要减去所有同时是三个数的倍数。

第三步:减去所有 p i ⋅ p j ⋅ p k p_i \cdot p_j \cdot p_k pipjpk的倍数

− ⌊ n p 1 ⋅ p 2 ⋅ p 3 ⌋ − ⌊ n p 1 ⋅ p 2 ⋅ p 4 ⌋ − . . . − ⌊ n p k − 2 ⋅ p k − 1 ⋅ p k ⌋ - \lfloor \frac{n}{p_1 \cdot p_2 \cdot p_3} \rfloor - \lfloor \frac{n}{p_1 \cdot p_2 \cdot p_4} \rfloor - ... - \lfloor \frac{n}{p_{k-2} \cdot p_{k-1} \cdot p_k} \rfloor p1p2p3np1p2p4n...pk2pk1pkn

所以接下来就是加上所有四个质数乘积分之 n n n,再减去所有五个质数乘积分之 n n n

将前面给出的欧拉公式的计算公式展开,就可以发现和这个加加减减的式子是相等的,即:
ϕ ( n ) = n − ⌊ n p 1 ⌋ − ⌊ n p 2 ⌋ − . . . − ⌊ n p k ⌋ + ⌊ n p 1 ⋅ p 2 ⌋ + ⌊ n p 1 ⋅ p 3 ⌋ + . . . + ⌊ n p k − 1 ⋅ p k ⌋ − ⌊ n p 1 ⋅ p 2 ⋅ p 3 ⌋ − ⌊ n p 1 ⋅ p 2 ⋅ p 4 ⌋ − . . . − ⌊ n p k − 2 ⋅ p k − 1 ⋅ p k ⌋ . . . = n ⋅ ( 1 − 1 p 1 ) ⋅ ( 1 − 1 p 2 ) ⋅ . . . ⋅ ( 1 − 1 p k ) \begin{align} \phi(n) &= n - \lfloor \frac{n}{p_1} \rfloor - \lfloor \frac{n}{p_2} \rfloor - ... - \lfloor \frac{n}{p_k} \rfloor \\ & + \lfloor \frac{n}{p_1 \cdot p_2} \rfloor + \lfloor \frac{n}{p_1 \cdot p_3} \rfloor + ... + \lfloor \frac{n}{p_{k-1} \cdot p_k} \rfloor \\ & - \lfloor \frac{n}{p_1 \cdot p_2 \cdot p_3} \rfloor - \lfloor \frac{n}{p_1 \cdot p_2 \cdot p_4} \rfloor - ... - \lfloor \frac{n}{p_{k-2} \cdot p_{k-1} \cdot p_k} \rfloor \\ & ... \\ & = n \cdot (1 - \frac{1}{p_1}) \cdot (1 - \frac{1}{p_2}) \cdot ... \cdot (1 - \frac{1}{p_k}) \end{align} ϕ(n)=np1np2n...pkn+p1p2n+p1p3n+...+pk1pknp1p2p3np1p2p4n...pk2pk1pkn...=n(1p11)(1p21)...(1pk1)

用眼睛看也能看出上下两个式子相等,首先把上面式子里的 n n n提取出来,就是下面式子最外面的 n n n。然后上面式子里的开头1就是下面式子乘法展开时候每一项选择前面的1的结果。上面式子里的 1 p i \frac{1}{p_i} pi1就是下面式子里只有 ( 1 − 1 p i ) (1 - \frac{1}{p_i}) (1pi1)选择了 − 1 p i - \frac{1}{p_i} pi1,其它式子都选择1的结果,因此符号一定是负的。上面式子里的 1 p i ⋅ p j \frac{1}{p_i \cdot p_j} pipj1就是下面式子里只有 ( 1 − 1 p i ) (1 - \frac{1}{p_i}) (1pi1) ( 1 − 1 p j ) (1 - \frac{1}{p_j}) (1pj1)选择了 − 1 p i - \frac{1}{p_i} pi1 − 1 p j - \frac{1}{p_j} pj1,其它式子都选择1的结果,因此符号一定是正的。以此类推。

例题:AcWing 873. 欧拉函数

根据前面推导出的计算公式,只要用最开始的 n n n,每次抠出质因数 p i p_i pi,然后乘上 ( 1 − 1 p i ) (1 - \frac{1}{p_i}) (1pi1),这里可以改写成除以 p i p_i pi再乘以 p i − 1 p_i - 1 pi1,避免了浮点运算的同时也防止乘爆了溢出。

#include <iostream>

using namespace std;

int phi(int n) {
    int res = n;
    for (int i = 2; i * i <= n; i ++ ) {
        if (n % i) continue;
        res = res / i * (i - 1);
        while (n % i == 0) n /= i;
    }
    if (n != 1) res = res / n * (n - 1);
    return res;
}

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

原文地址:https://blog.csdn.net/SHU15121856/article/details/145149826

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!