写在前面
关于互质的定义,可以看我的这篇帖子: 互质
关于欧拉公式,可以看这篇(还没更新,可以看代码中的注释)
关于快速幂算法, 可以看我的这篇(还没更新,可以看代码中的注释)
关于求模运算,可以看我的这篇: 模运算
算法思路
根据欧拉函数,我们可以知道,要求就是
E
u
l
e
r
(
a
b
)
Euler(a^{b})
Euler(ab)
然后如果直接求,我们一定会因为因为
a
b
a^{b}
ab 数字太大求欧拉的复杂度太高,因此可以通过欧拉函数的性质(
[
E
u
l
e
r
(
a
b
)
=
E
u
l
e
r
(
a
)
∗
a
b
−
1
]
[Euler(a^{b})=Euler(a) * a^{b-1}]
[Euler(ab)=Euler(a)∗ab−1]),我们只需要得到底数
a
a
a的互质个数,再结合快速幂算法简化 a^{b-1}运算, 就可以得到
E
u
l
e
r
(
a
b
)
Euler(a^{b})
Euler(ab)了
代码如下
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Scanner;
public class main {
/**
* 给定 a, b,求 1 ≤ x < a^b 中有多少个 x 与 a^b 互质。由于答案可能很大, 你只需要输出答案对 998244353 取模的结果。
*
* 输入格式: 输入一行包含两个整数分别表示 a, b,用一个空格分隔。
*
* 输出格式: 输出一行包含一个整数表示答案。 样例输入 2 5 样例输出 16
*/
private static long mod = 998244353L;
private static long a, b;
// 以下是输入输出流代码, 在某些情况下可以提高通过率,在这个题会导致无法全对, 原因不明
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static long nextLong() throws Exception {
st.nextToken();
return (long) st.nval;
}
// 主函数
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
a = sc.nextLong();
b = sc.nextLong();
long res = Euler(a);
res = res%mod * Quick_pow(a, b - 1)% mod;
System.out.print(res);
}
// 欧拉函数计算公式
// 欧拉函数定义式: 对于 n = p1^k1 * p2^k2 * .. * pm^km (p1, p2, p3.. pm 为 n 的质因数)
// Euler(n) = n * (1-1/p1) * (1-1/p2) * .. * (1-1/pm)
// 欧拉函数性质: 若 n mod x = 0, 即 n 能被 x 整除, 且 n/x mod x =0, 即 n/x 能够被x 整除, 则 Euler(n)
// = Euler(n/x) * x
// 因此 对于 Euler(a^b) = Euler(a^(b-1)) * a = Euler(a^(b-2)) * a^2 = .. = Euler(a)
// * a^(b-1)
// 以下函数正是计算: Euler(a)
private static long Euler(long n) {
long res = n;
for (long i = 2; i * i <= n; ++i) {
if (n % i == 0) {
res = res / i * (i - 1); // 尽量先除, 再乘, 避免越域风险
while (n % i == 0) {
n = n / i;
}
}
}
if (n > 1) {
res = res / n * (n - 1);
}
return res;
}
// 快速求幂算法
// 如果指数是偶数: 那么指数除以2, 底×底
// 如果指数是奇数: 那么提取出指数, 再将剩下的指数除以2
// 以上循环, 直到 剩下的指数 为0
// 结果: 多个以奇数为指的数相乘
// 例如: 10^100 = 100^50 = 10000^25 = 10000 * 100000000^12, 循环次数从100降低成了12
// 特殊性: 这里题目是要求互质的个数与998244353L求模:
// 根据欧拉函数的公式: Euler(n) = n*(1-1/p1)*(1-1/p2)...*(1-1/pn) (pi为0的质因数)
// 因此题目的结果表达式: r = Euler(n) % (998244353L) = [Euler(a) * a^(b-1)] % (998244353L)
// 根据 模的 模乘法分配律: (a*b)%n = [(a%n)*(b%n)]%n = (a%n*b%n)%n
// r = [Euler(a) % (998244353L) * a^(b-1) % (998244353L)] % (998244353L)
// 以下函数正是计算: a^(b-1) % (998244353L)
// 算法思路: 使用快速求幂算法降低循环运行的次数
// 假设 a^b = a1*a2*a3... an (ai 指数均为1, 且 ai 一定为 a 的 指数倍, 因为 ai 为缩小幂的时候提取出来的)
// 那么 a^b % (998244353L) = [a1 % (998244353L) * a2 % (998244353L).. * an %
// (998244353L)] % (998244353L)
// 由于每个ai 都是 a 的指数倍, 因此不妨设: ai = a^m
// 再次根据 模的乘法分配律: ai % (998244353L)= a^m % (998244353L) = [a % (998244353L)..*a %
// (998244353L)]% (998244353L)
// 即每次提取出底的时候都对其取模, 这样做可以保证不溢出域
private static long Quick_pow(long a, long b) {
// a: 底数 b: 指数
long res = 1; // 结果
while (b > 0) {
// 如果指数是偶数
if (b % 2 == 0) {
b = b / 2;
a = a % mod * a % mod;
} else {
// 如果 指数为奇数, 提取当前的a
b = b - 1;
res = res % mod * a % mod;
}
}
return res % mod;
}
}