欧拉函数
5. 示例代码(Java)
以下是使用质因数分解法计算欧拉函数的代码:
import java.util.HashSet;
import java.util.Set;
public class EulerTotientFunction {
// 质因数分解并返回不同的质因数集合
public static Set<Integer> primeFactors(int n) {
Set<Integer> factors = new HashSet<>();
while (n % 2 == 0) {
factors.add(2);
n /= 2;
}
for (int i = 3; i * i <= n; i += 2) {
while (n % i == 0) {
factors.add(i);
n /= i;
}
}
if (n > 1) {
factors.add(n);
}
return factors;
}
// 计算欧拉函数 φ(n)
public static int eulerTotient(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int result = n;
Set<Integer> factors = primeFactors(n);
for (int p : factors) {
result = result / p * (p - 1);
}
return result;
}
// 测试程序
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
System.out.println("φ(" + i + ") = " + eulerTotient(i));
}
}
}
以下是使用递归分解法计算欧拉函数的代码:
public class EulerTotient {
// 计算phi(n)
public static int phi(int n) {
if (n == 1) return 1;
int result = n;
// 处理质因数2
if (n % 2 == 0) {
result -= result / 2; // 等价于 result * (1-1/2)
while (n % 2 == 0) n /= 2;
}
// 处理奇质因数
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) {
result -= result / i; // 等价于 result * (1-1/i)
while (n % i == 0) n /= i;
}
}
// 若n仍有大于sqrt(n)的质因数
if (n > 1) {
result -= result / n;
}
return result;
}
public static void main(String[] args) {
System.out.println("phi(4) = " + phi(4)); // 输出2
System.out.println("phi(6) = " + phi(6)); // 输出2
}
}
阶乘的和
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
//本题需要找到合并阶乘的规律
//如果题目中没有A[i]阶乘加和的条件,那么m就是数组A中的最小值
//而如果要对A[i]的阶乘加和,可能出现两个较小的数的阶乘合并成一个较大数的阶乘的现象
//如3个2的阶乘可以合并成3的阶乘,4个3的阶乘可以合并成4的阶乘
//故得到规律:若数字num出现了cnt次,且num可以被(cnt+1)整除,那么就可以合并成(num+1)的阶乘
// 所以本题就是要让m从nums[]中最小的元素开始,不断验证(每次按m+1验证)这个min可不可以合并,可以就合并
// 下面举例说明为什么要将nums[]中最小的元素作为因子初始值
// 3! 6
// 4! 24
// 5! 120
// 和为150 必是 3!的倍数! 因为 4!5!都是3!的倍数,所以加起来肯定也是 (min) 3!的倍数
// 所以应该找出数组最小值作为初始的sum,再一个一个往上找
// 比如上面这个例子,需要有 4个3!才能进化为 4!,由于只有一个 3!所以不能往上找了,break退出,答案就是3
// 如果是nums的元素为3 3 3 3 4 5,则将3合并,结果为4 4 5
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long[] nums = new long[n];
long min = Long.MAX_VALUE;
for (int i = 0; i < nums.length; i++) {
nums[i] = sc.nextLong();
min = Math.min(min, nums[i]);
}
long sum = min; // sum为nums[]中的最小值
long num = 0; // num为nums[]中值为sum的数量
// 开始合成
// 18个8!可以合成2个9!
while (true) {
// 如下文注释:num在上个循环中是18,sum 在上个循环中 8更新为9
// 根据 18 * 8! = 2 * 9 * 8! = 2 * 9!可知此次循环的 num 要更新为 18 / 9 = 2
// 说明18 * 8!这个数的因数不但8!可以,9!也可以,因为求max因数,所以取9!
// 所以解法:
// 从nums[]中最小的元素作为因子初始值开始,找num*sum的组合,不断向上找可以符合这个组合的更大的sum,
// 找到第一个sum+1不符合,则sum即为所求
num = num / sum;
// num不但要算minNum合并后新num的个数,还要加上nums原本还剩几个新num,加一块才是新num在合并后在nums里的个数
for (int i = 0; i < nums.length; i++) {
if (nums[i] == sum) {
num++;
}
}
// 如果sum的数量(num)等于sum+1的倍数,则可以将其逻辑上将这num个sum合成为(sum+1)的阶乘。
// 形象化: sum = 2 num = 3 3个2 = 2+2+2 sum+1 = 3 3 = 3的倍数, 3个2合成为3的阶乘
// 18(num)个8!(sum) = 18 * 8! = 2 * 9 * 8! = 2 * 9! sum的数量18 = sum+1=9 的倍数
if (num % (sum + 1) == 0 && num != 0) {
// 说明还有更大的因数能与num分配成符合题意的组合,那么继续更新更大的因数
sum++;
} else break;
}
System.out.println(sum);
}
}
公因数匹配
思路引用:冯勒布
//本题的思路比较巧妙,利用了分解质因子和查找质因子的思想
//对于输入的每个元素,都拆解其质因子,若该质因子是首次出现则存入map,记录其出现的位置
//若该质因子不是首次出现,说明在该元素的前面已经有元素具有共同的质因子,找到了一对下标(i,j)
//由于题目要求i和j都尽可能地小,因此要不断比较出最小的(i,j)再输出
//附:本题若暴力求解可使用gcd函数求最大公因数,可立即通过4个测试点
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] arr = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = scan.nextInt();
}
Map<Integer, Integer> map = new HashMap<>();
int start = -1, end = -1;
for (int j = 0; j < arr.length; j++) {
int temp = arr[j];
int i = 2;
while (i * i <= temp) {
if (temp % i == 0) {
if (!map.containsKey(i)) {
map.put(i, j);
} else {
// 只有当当前 pair 更优时才更新
int prevIndex = map.get(i);
if (start == -1 ||
(prevIndex < start) ||
(prevIndex == start && j < end)) {
start = prevIndex;
end = j;
}
}
temp /= i;
i = 2;
} else {
i++;
}
}
// 处理剩余的大于1的部分(也是一个质因子)
if (temp > 1) {
if (!map.containsKey(temp)) {
map.put(temp, j);
} else {
int prevIndex = map.get(temp);
if (start == -1 ||
(prevIndex < start) ||
(prevIndex == start && j < end)) {
start = prevIndex;
end = j;
}
}
}
}
if (end != -1) {
System.out.println((start + 1) + " " + (end + 1));
}
scan.close();
}
}