题解 UVA10299
前言
数学符号约定:
- 
\(a\)、\(b\)、\(m\)、\(n\)、\(x\)、\(y\):任意一个正整数。 
- 
\(p\):任意一个质数。 
- 
\(d\):一个数的任意一个因子。 
- 
\(\varphi(n)\):欧拉函数。 
- 
\(d \mid n\):\(d\) 能整除 \(n\) 或 \(d\) 是 \(n\) 的因子。 
- 
\(m \perp n\):\(m\) 和 \(n\) 互质。 
若无特殊说明,则本篇文章的数学符号将会严格按照上述约定编写。
题目描述:
给定一个正整数 \(n \, (n \le 10^9)\),求 \(\varphi (n)\),有多组数据。
题目分析:
这是一个非常非常裸的欧拉函数板子,鉴于可能有同学不知道欧拉函数,本篇文章就简单介绍一下欧拉函数。
欧拉函数:
- 
定义:对于正整数 \(n\),欧拉函数是所有小于等于 \(n\) 的数中与 \(n\) 互质的数的个数。 
- 
计算式:\(\varphi(n) = n \times \prod_{p \mid n}\frac{p-1}{p}\) 
- 
推论:\(\forall p \in Primes \,,\, \varphi(p)=p-1\) 
由欧拉函数的计算式,我们可以看出,对于求出 \(\varphi(n)\) 的值,我们可以先用线性筛预处理出 \(\sqrt n\) 个质数,然后用筛出来的质数来求出 \(\varphi(n)\) 的值。
然而,这里有一个小 trick,就是我们不需要对所有的 \(n\) 用上述方法求 \(\varphi(n)\) 的值,因为根据欧拉函数的推论可知,对于所有的质数,其欧拉函数的值等于小于自己的所有正整数的个数。
所以我们可以先行判断 \(n\) 是否为质数,如果为质数,则直接输出 \(n-1\),否则计算其 \(\varphi(n)\) 的值。
对于质数的判定,我们这里可以使用时间复杂度为 \(\Theta(\lg n)\) 的 Miller-Rabin 素性测试。
Miller-Rabin 素性测试:
首先,我们先了解一下费马小定理:
对此,我们可以有一个 idea,对于一个数 \(n\),我们先随机一个 \(a\),判断一下是否有 \(a^{n-1} \equiv 1 \pmod n\),如果该式不成立,则我们可以判定 \(n\) 一定为合数。
但是对此,我们却不能确定 \(n\) 一定是质数,因为存在像 \(561\) 这样的 CarMichael 数(也叫费马伪素数)无法判断出来。
对此,我们引入二次探测定理:若 \(p\) 为奇素数,且 \(0 < x < p\),则 \(x^2 \equiv 1 \pmod p\) 的解为 \(x\equiv 1 \pmod p\) 或 \(x \equiv p-1 \pmod p\)。
Miller-Rabin 素性测试就是将费马小定理与二次探测定理结合使用来判断一个数是否有极大概率为质数,他的具体做法如下:
- 
将 \(n-1\) 表示为 \(u \times 2^t\),先测试 \(a^{u \times 2^t} \equiv 1 \pmod n\) 是否成立,若不成立,则证明 \(n\) 为合数,若成立,则进行下一步。 
- 
若 \(a^u \equiv 1 \pmod n\) 成立,或者存在某个整数 \(0 \le y \le t-1\),使得 \(a^{u \times 2^y} \equiv 1 \pmod n\) 成立,则称 \(n\) 通过 Miller 测试。 
Miller-Rabin 的算出错误结果的概率小于等于 \(\dfrac 14\),若反复测试 \(k\) 次,则算出错误结果的概率小于等于 \((\dfrac 14)^k\),这里建议测试次数为 \(20\) 次左右。
代码实现:
#include <bits/stdc++.h>
using namespace std;
#define MAX_SIZE (int)6e5
#define int long long
long long primes[MAX_SIZE];
bool nf[MAX_SIZE];
long long fastpow(long long base, long long index, long long mod) { //快速幂
    long long ans = 1;
    while (index) {
        if (index & 1)
            ans = ans % mod * (base % mod) % mod;
        base = base % mod * (base % mod) % mod;
        index >>= 1;
    }
    return ans;
}
void primeshai(long long num) { //线性筛
    int fin = sqrt(num);
    for (int i = 2; i <= fin; i++) {
        if (!nf[i])
            primes[++primes[0]] = i;
        for (int j = 1; j <= primes[0] && primes[j]*i <= fin; j++) {
            nf[primes[j]*i] = 1;
            if (i % primes[j] == 0)
                break;
        }
    }
}
long long phi(long long num) { //计算欧拉函数
    long long fin = sqrt(num);
    long long ans = num;
    for (long long i = 1; i <= primes[0] && primes[i] <= fin; i++) {
        if (num % primes[i] == 0) {
            ans /= primes[i];
            ans *= (primes[i] - 1);
        }
        while (num % primes[i] == 0)
            num /= primes[i];
    }
    if (num > 1) {
        ans /= num;
        ans *= (num - 1);
    }
    return ans;
}
bool Miller_Rabin(long long n, int test_time = 20) { // Miller_Rabin 素性测试
    if (n < 3 || (n & 1) == 0)
        return n == 2;
    long long a = n - 1;
    long long b = 0;
    while (!(a & 2))
        a >>= 1, ++b;
    for (int i = 1, j; i <= test_time; ++i) {
        long long x = rand() % (n - 1) + 1;
        long long v = fastpow(x, a, n);
        if (v == 1)
            continue;
        for (j = 0; j < b; ++j) {
            if (v == n - 1)
                break;
            v = (long long)v * v % n;
        }
        if (j >= b)
            return 0;
    }
    return 1;
}
signed main() {
    primeshai(1e9 + 10); //预处理出根号1e9+10内质数
    srand(time(0));
    long long n;
    while (cin >> n) {
        if (n == 0)
            break;
        if (n == 1) //特判一下 n 为 1 的情况
            cout << 0 << endl;
        else if (Miller_Rabin(n)) //如果 n 为质数,则输出 n-1
            cout << n - 1 << endl;
        else
            cout << phi(n) << endl;
    }
    return 0;
}
Update 记录:
- \(2022.8.11\):修改了一些小的细节错误。

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号