当前位置: 首页 > news >正文

java 洛谷题单【数学1】基础数学问题

P1143 进制转换

解题思路

  1. 读取输入
    • 使用 Scanner 读取三个输入值:原进制 n,原进制数 s,目标进制 m。
  2. 进制转换
    • 使用 Integer.parseInt(s, n) 将原进制数 s 转换为十进制整数。
    • 使用 Integer.toString(decimalValue, m) 将十进制整数转换为目标进制字符串。
    • 调用 .toUpperCase() 确保输出中的字母为大写。
  3. 输出结果
    • 打印转换后的目标进制数。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner input = new Scanner(System.in);

        // 读取输入
        int n = input.nextInt(); // 原进制
        String s = input.next(); // 原进制数
        int m = input.nextInt(); // 目标进制

        // 关闭输入流
        input.close();

        // 将原进制数转换为目标进制
        int decimalValue = Integer.parseInt(s, n);
        String result = Integer.toString(decimalValue, m).toUpperCase();

        // 输出结果
        System.out.println(result);
    }
}

P1469 找筷子 

解题思路

对于对内存有要求的题目不太适合使用Java完成,并且有规定不保证非C/C++语言可以通过题目,洛谷不会给非C/C++语言提供额外的时间和空间,所以该题大家最好使用C/C++完成,Java代码仅供参考。

该题通过异或运算的思路可以完美解决配对问题,异或运算的法则是相同为0,不同为1

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int ans = 0;
        while (n-- > 0) {
            ans ^= scanner.nextInt();
        }
        System.out.println(ans);
        scanner.close();
    }
}

P1100 高低位交换 

解题思路

1. 读取输入

  • 使用 Scanner 读取一个整数 num
  • input.nextInt() 读取的是一个有符号的 32 位整数。
  • 为了将其视为无符号整数,使用按位与操作 & 0xFFFFFFFFL
    • 0xFFFFFFFFL 是一个 64 位的掩码,保留输入整数的低 32 位。
    • 这样可以将负数转换为其无符号等价值(例如,-1 会被转换为 4294967295)。

2. 高低位交换

  1. 提取低 16 位(unsignedNum & 0x0000ffff

    • 使用按位与操作 & 0x0000ffff 提取无符号数的低 16 位。
    • 例如,unsignedNum = 0x12345678,则低 16 位为 0x5678
  2. 将低 16 位移到高 16 位((unsignedNum & 0x0000ffff) << 16

    • 使用左移操作 << 16 将低 16 位移动到高 16 位的位置。
  3. 提取高 16 位(unsignedNum & 0xffff0000)

    • 使用按位与操作 & 0xffff0000 提取无符号数的高 16 位。
    • 例如,unsignedNum = 0x12345678,则高 16 位为 0x1234
  4. 将高 16 位移到低 16 位((unsignedNum & 0xffff0000) >> 16)

    • 使用右移操作 >> 16 将高 16 位移动到低 16 位的位置。
    • 注意:由于 unsignedNum 是 long 类型,右移操作不会保留符号位。
  5. 合并高低位(((unsignedNum & 0x0000ffff) << 16 | (unsignedNum & 0xffff0000) >> 16))

    • 使用按位或操作 | 将交换后的高位和低位合并,得到最终结果。

3. 输出结果

import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner input = new Scanner(System.in);
        // 将读入的数转为无符号整型
        long unsignedNum = input.nextInt() & 0xFFFFFFFFL;

        // 输出结果
        System.out.println(((unsignedNum & 0x0000ffff) << 16 | (unsignedNum & 0xffff0000) >> 16));

        input.close();
    }
}

P1017 [NOIP 2000 提高组] 进制转换 

解题思路

  1. 输入读取

    • 使用 Scanner 读取十进制数 n 和负进制基数 r
  2. 负进制转换逻辑

    • 使用 n % r 计算余数。
    • 如果余数为负数,则调整余数为正数,同时将商 n 加 1。
    • 将余数转换为对应的字符:
      • 如果余数大于等于 10,使用 A 到 F 表示(类似于 16 进制)。
      • 否则直接添加数字。
  3. 结果构建

    • 使用 StringBuilder 构建结果,最后调用 reverse() 方法将结果反转(因为进制转换是从低位到高位计算的)。
  4. 特殊情况

    • 如果输入的 n 为 0,直接输出 0
  5. 输出格式

    • 按照题目要求输出:n=结果(baseR)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner input = new Scanner(System.in);

        // 读取输入的十进制数 n 和负进制基数 R
        int n = input.nextInt();
        int r = input.nextInt();

        // 保存原始输入值,用于输出
        int originalN = n;

        // 用 StringBuilder 构建结果
        StringBuilder result = new StringBuilder();

        // 负进制转换
        while (n != 0) {
            int remainder = n % r;
            n /= r;

            // 如果余数为负数,调整余数和商
            if (remainder < 0) {
                remainder += Math.abs(r);
                n += 1;
            }

            // 将余数转换为对应的字符(支持大于 10 的数码)
            if (remainder >= 10) {
                result.append((char) ('A' + (remainder - 10)));
            } else {
                result.append(remainder);
            }
        }

        // 如果结果为空,表示输入为 0
        if (result.length() == 0) {
            result.append("0");
        }

        // 输出结果
        System.out.printf("%d=%s(base%d)\n", originalN, result.reverse().toString(), r);

        input.close();
    }
}

P1866 编号

解题思路

        1.排序:将兔子的编号范围按升序排序,优先处理编号范围较小的兔子。

        2.计算可选编号:对于第 i 只兔子,可选编号数量为 M[i] - i,因为前面 i 只兔子已经占用了 i 个编号。

        3.取模运算:每次乘法操作后对 10^9+7 取模,避免溢出。

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int[] m = new int[n];
        
        for (int i = 0; i < m.length; i++) {
            m[i] = input.nextInt();
        }

        input.close();
        Arrays.sort(m);

        // mod
        final int MOD = 1_000_000_007;
        long ans = 1;

        for (int i = 0; i < n; i++) {
            int choices = m[i] - i;
            if (choices <= 0) {
                ans = 0;
                break;
            }
            ans = (ans * choices) % MOD;
        }
        System.out.println(ans);
    }
}

P2822 [NOIP 2016 提高组] 组合数问题

解题思路

(1) 初始化组合数
  • 使用递推公式计算组合数: $ \binom{i}{j} = \binom{i-1}{j-1} + \binom{i-1}{j} $
  • 每次计算后对 $k$ 取模,避免溢出。
  • 如果 $\binom{i}{j} \mod k = 0$,则统计该组合数。
(2) 计算前缀和
  • 对于每行 $i$,计算满足条件的组合数的前缀和。
  • 前缀和公式:
    • 如果 $j < i$prefixSum[i][j] = prefixSum[i - 1][j] + count[i]
    • 如果 $j == i$(对角线特殊处理):prefixSum[i][j] = count[i] + prefixSum[i - 1][j - 1]
(3) 查询结果
  • 对于每个测试用例,读取$n$$m$
  • 确保 $m \leq n$,因为列数不能超过行数。
  • 直接通过 prefixSum 查询满足条件的组合数个数。
import java.util.Scanner;

public class Main {
    static long[][] comb = new long[2001][2001]; // 存储组合数
    static long[] count = new long[2001]; // 每行满足条件的组合数个数
    static long[][] prefixSum = new long[2001][2001]; // 前缀和数组
    static long n, m, k;
    static int t;

    public static void main(String[] args) {
        Scanner ipnut = new Scanner(System.in);
        t = ipnut.nextInt();
        k = ipnut.nextLong();

        // 初始化组合数
        comb[0][0] = 1;

        for (int i = 1; i < 2001; i++) {
            comb[i][0] = 1;
            for (int j = 1; j <= i; j++) {
                // 递推计算组合数并取模
                comb[i][j] = (comb[i - 1][j - 1] + comb[i - 1][j]) % k;
                if (comb[i][j] == 0) {
                    // 统计每行满足条件的组合数个数
                    count[i]++;
                }
                // 计算前缀和
                prefixSum[i][j] = prefixSum[i - 1][j] + count[i];
                if (j == i) {
                    // 特殊处理对角线
                    prefixSum[i][j] = count[i] + prefixSum[i - 1][j - 1];
                }
            }
        }

        while (t-- > 0) {
            // 读取输入
            n = ipnut.nextLong();
            m = ipnut.nextLong();
            // 确保列数不超过行数
            if (m > n) {
                m = n;
            }
            // 输出结果
            System.out.println(prefixSum[(int) n][(int) m]);
        }
        ipnut.close();
    }
}

P2789 直线交点数

解题思路

本来没有思路,看了题解才明白,代码思路来源于洛谷题解。觉得有帮助的可以给题解作者点个免费的赞。

import java.util.Scanner;

public class Main {
    static int n;
    static int MAX = -1;
    static int ans = 0;
    static boolean[] f = new boolean[11000];

    /**
     * 递归函数,用于计算所有可能的结果
     * 
     * @param n 剩余的数
     * @param k 当前计算的结果
     */
    public static void func(int n, int k) {
        // 如果剩余的数为 0,记录当前结果
        if (n == 0) {
            f[k] = true;                // 标记结果 k 为可达
            MAX = Math.max(k, MAX);     // 更新最大值
        } else {
            // 遍历所有可能的相交方式
            for (int r = n; r >= 1; r--) {
                // 递归调用,计算剩余部分
                func(n - r, r * (n - r) + k);
            }
        }
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        n = input.nextInt();
        func(n, 0);

        for (int i = 0; i <= MAX; i++) {
            if (f[i]) {
                ans++;
            }
        }

        System.out.println(ans);
        input.close();
    }
}

P3913 车的攻击

解题思路

1. 排序

  • 对 rows 和 cols 数组进行排序:
    • 排序的目的是为了方便后续统计数组中不同元素的数量(去重)。

2. 统计不同行和列的数量

  • 调用 countDistinct 方法分别统计 rows 和 cols 中不同元素的数量:
    • r 表示不同的行数。
    • c 表示不同的列数。
  • countDistinct 方法的逻辑:
    • 遍历排序后的数组,统计相邻元素不同的次数,即为数组中不同元素的数量。

3. 计算覆盖的格子数量

  • 使用公式计算覆盖的格子数量:
    long result = (long) N * (r + c) - (long) r * c;
    • (N * (r + c)):表示所有被覆盖的行和列的格子数。
    • -(r * c):减去行列交叉的格子数,因为这些格子会被重复计算。
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        // 使用 BufferedReader 读取输入,提高输入效率
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // 使用 StringTokenizer 解析第一行输入,获取 N 和 K
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken()); // 矩阵的大小 N x N
        int K = Integer.parseInt(st.nextToken()); // 特殊点的数量 K

        // 定义两个数组分别存储特殊点的行和列
        int[] rows = new int[K];
        int[] cols = new int[K];

        // 读取 K 个特殊点的行列坐标
        for (int i = 0; i < K; i++) {
            st = new StringTokenizer(br.readLine());
            rows[i] = Integer.parseInt(st.nextToken()); // 读取行坐标
            cols[i] = Integer.parseInt(st.nextToken()); // 读取列坐标
        }

        // 对行和列的数组进行排序,方便后续去重统计
        Arrays.sort(rows);
        Arrays.sort(cols);

        // 统计行和列中不同的值的数量
        int r = countDistinct(rows); // 不同行的数量
        int c = countDistinct(cols); // 不同列的数量

        // 计算结果:总覆盖的格子数
        // (N * (r + c)) 表示所有被覆盖的行和列的格子数
        // 减去 (r * c),因为行列交叉的格子会被重复计算
        long result = (long) N * (r + c) - (long) r * c;

        // 输出结果
        System.out.println(result);
    }

    /**
     * 统计数组中不同元素的数量
     * @param array 已排序的数组
     * @return 不同元素的数量
     */
    private static int countDistinct(int[] array) {
        if (array.length == 0)
            return 0; // 如果数组为空,返回 0

        int count = 1; // 至少有一个不同的元素
        for (int i = 1; i < array.length; i++) {
            // 如果当前元素与前一个元素不同,计数加一
            if (array[i] != array[i - 1]) {
                count++;
            }
        }
        return count; // 返回不同元素的数量
    }
}

P2638 安全系统

解题思路

1. 核心功能

代码的核心是计算组合数(即数学中的 C(n, r)),并将结果进行某种组合运算后输出。组合数的公式为:C(n, r) = \frac{n!}{r! \cdot (n - r)!}

由于可能涉及非常大的数,代码使用了 Java 的 BigInteger 类来处理大数运算。


2. 代码结构

func 方法
  • 该方法用于计算组合数 C(n, r)
  • 通过迭代的方式计算组合数,避免直接计算阶乘(因为阶乘增长非常快,容易导致性能问题或溢出)。
  • 具体实现:
    • 使用一个循环从 1 到 r,逐步计算组合数的分子和分母。
    • 分子:n * (n - 1) * ... * (n - r + 1)
    • 分母:r!
    • 每一步都用 BigInteger 的 multiply 和 divide 方法进行计算。
main 方法
  • 主方法负责读取输入、调用计算方法并输出结果。
  • 输入:
    • 从控制台读取三个 BigInteger 类型的数:n、a 和 b。
  • 计算:
    • 调用 func 方法计算两个组合数:
      • func(n, a + n)
      • func(n, b + n)
    • 将两个组合数的结果相乘。
  • 输出:
    • 使用 write 方法输出最终结果。
import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    // 输出 BigInteger 类型的结果
    public static void write(BigInteger x) {
        System.out.print(x);
    }

    // 计算组合数
    public static BigInteger func(BigInteger r, BigInteger n) {
        BigInteger ans = BigInteger.ONE;
        // 计算组合数的公式 C(n, r) = n! / (r! * (n - r)!)
        // 这里使用了 BigInteger 的方法来处理大数
        for (BigInteger i = BigInteger.ONE; i.compareTo(r) <= 0; i = i.add(BigInteger.ONE)) {
            ans = ans.multiply(n.subtract(i).add(BigInteger.ONE));
            ans = ans.divide(i);
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        BigInteger n = input.nextBigInteger();
        BigInteger a = input.nextBigInteger();
        BigInteger b = input.nextBigInteger();
        input.close();
        // 代入公式计算并输出结果
        write(func(n, a.add(n)).multiply(func(n, b.add(n))));
    }
}

P1246 编码

解题思路

  1. 输入验证:检查输入字符串是否由小写字母组成,并且每个字符严格递增。

  2. 计算前缀和:统计所有比当前单词长度短的合法单词数目之和。

  3. 计算当前长度内的位置:使用组合数学的方法计算当前单词在其长度内的位置。

  4. 结果输出:将前缀和与当前长度内的位置相加得到最终编码。

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        // 使用 BufferedReader 读取输入
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine().trim(); // 读取并去除首尾空格
        int len = s.length(); // 获取输入字符串的长度

        // 检查字符串长度是否符合要求(1 <= len <= 6)
        if (len < 1 || len > 6) {
            System.out.println(0); // 长度不合法,输出 0
            return;
        }

        int[] chars = new int[len]; // 用于存储字符对应的字母序号
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i); // 获取字符串中的每个字符
            // 检查字符是否为小写字母
            if (c < 'a' || c > 'z') {
                System.out.println(0); // 非法字符,输出 0
                return;
            }
            chars[i] = c - 'a' + 1; // 将字符转换为字母序号(a=1, b=2, ..., z=26)
        }

        // 检查字符是否按严格递增顺序排列
        for (int i = 0; i < len - 1; i++) {
            if (chars[i] >= chars[i + 1]) {
                System.out.println(0); // 非递增,输出 0
                return;
            }
        }

        int m = len; // 字符串长度
        long sum = 0; // 用于存储比当前长度短的所有合法字符串的数量

        // 计算比当前长度短的所有合法字符串数量
        for (int i = 1; i < m; i++) {
            sum += comb(26, i); // 从 26 个字母中选 i 个的组合数
        }

        long pos = 0; // 当前字符串在所有合法字符串中的相对位置
        int prev = 0; // 上一个字符的字母序号

        // 计算当前字符串的相对位置
        for (int i = 0; i < m; i++) {
            int current = chars[i]; // 当前字符的字母序号
            int start = prev + 1; // 起始字母序号
            int end = current - 1; // 结束字母序号
            // 遍历从 start 到 end 的所有可能字符
            for (int j = start; j <= end; j++) {
                int remain = m - i - 1; // 剩余未处理的字符数
                pos += comb(26 - j, remain); // 计算剩余字符的组合数
            }
            prev = current; // 更新上一个字符的字母序号
        }
        pos += 1; // 加上当前字符串本身的位置

        // 输出结果:比当前字符串小的所有合法字符串数量 + 当前字符串的位置
        System.out.println(sum + pos);
    }

    // 计算组合数 C(n, k)
    private static int comb(int n, int k) {
        if (k < 0 || k > n) // 如果 k 不合法,返回 0
            return 0;
        if (k == 0 || k == n) // 如果 k 为 0 或 n,返回 1
            return 1;
        if (k > n / 2) // 优化计算,C(n, k) = C(n, n-k)
            k = n - k;
        int result = 1; // 存储结果
        for (int i = 1; i <= k; i++) {
            result = result * (n - k + i) / i; // 使用公式计算组合数
        }
        return result;
    }
}

P2926 [USACO08DEC] Patting Heads S

解题思路

  1. 内存优化策略

    • 使用 HashMap 替代大数组:当数值范围较大时(如最大值为1e9),传统数组会占用过多内存。哈希表只存储实际存在的数值,显著减少内存消耗。

    • 按需计算:仅处理实际存在的数值,避免遍历整个数值范围。

  2. 核心算法逻辑

    • 因数替代倍数:原题要求统计数值的倍数,但优化后改为统计因数。因为当数值范围很大时,因数的数量远小于倍数的数量。

    • 预处理机制:预先计算每个数值的拍打次数并存储,后续查询时间复杂度降为O(1)。

  3. 关键操作说明

    • 因数生成:通过遍历到\sqrt{n}来高效获取所有因数,时间复杂度为O(\sqrt{n})

    • 拍打次数计算:对每个数值的因数集合,累加这些因数在输入中的出现次数总和。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int N = input.nextInt();        // 读取牛的总数
        int[] A = new int[N];           // 存储每头牛的数值
        Map<Integer, Integer> countMap = new HashMap<>(); // 统计数值出现次数

        // 统计每个数的出现次数
        for (int i = 0; i < N; i++) {
            A[i] = input.nextInt();
            // 使用哈希表记录数值出现次数(自动处理稀疏数据)
            countMap.put(A[i], countMap.getOrDefault(A[i], 0) + 1);
        }

        // 预处理每个数值的拍打次数
        Map<Integer, Integer> patsMap = new HashMap<>(); // 存储数值对应的拍打总次数
        for (int num : countMap.keySet()) { // 只处理实际存在的数值
            int sum = 0;
            // 获取当前数值的所有因数(包括1和自身)
            List<Integer> divisors = getDivisors(num);
            // 累加所有因数的出现次数
            for (int d : divisors) {
                sum += countMap.getOrDefault(d, 0);
            }
            patsMap.put(num, sum); // 存储计算结果
        }

        // 输出结果(需减去自身的一次计数)
        for (int x : A) {
            System.out.println(patsMap.get(x) - 1); 
        }

        input.close();
    }

    // 生成一个数的所有因数
    private static List<Integer> getDivisors(int n) {
        List<Integer> divisors = new ArrayList<>();
        // 遍历到sqrt(n)以优化效率
        for (int i = 1; i <= Math.sqrt(n); i++) {
            if (n % i == 0) {
                divisors.add(i);          // 添加较小因数
                int other = n / i;        // 计算对应较大因数
                if (other != i) {         // 避免平方数重复添加
                    divisors.add(other);
                }
            }
        }
        return divisors;
    }
}

P3383 【模板】线性筛素数

解题思路

  1. 线性筛法

    • 使用 isComposite 数组标记非素数。
    • 通过 primes 列表存储所有素数。
    • 保证每个数只被其最小的素因子标记一次,时间复杂度为 $O(n)$
  2. 查询处理

    • 直接通过 primes.get(k - 1) 获取第 $k$ 小的素数
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));

        // 读取 n 和 q
        String[] firstLine = br.readLine().split(" ");
        int n = Integer.parseInt(firstLine[0]);
        int q = Integer.parseInt(firstLine[1]);

        // 线性筛法求素数
        List<Integer> primes = new ArrayList<>();
        boolean[] isComposite = new boolean[n + 1];
        for (int i = 2; i <= n; i++) {
            if (!isComposite[i]) {
                primes.add(i);
            }
            for (int prime : primes) {
                if (i * prime > n) break;
                isComposite[i * prime] = true;
                if (i % prime == 0) break;
            }
        }

        // 处理查询
        for (int i = 0; i < q; i++) {
            int k = Integer.parseInt(br.readLine());
            pw.println(primes.get(k - 1)); // 第 k 小的素数
        }

        pw.flush();
    }
}

P1835 素数密度

解题思路

  1. 埃拉托色尼筛法

    • 先标记区间 [l, r] 中所有数为素数。
    • 从小于等于 sqrt(r) 的所有素数开始,将它们的倍数标记为非素数。
    • 通过计算起始位置 start,避免从 1 开始筛选,直接筛选区间内的数。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        long l = input.nextLong();
        long r = input.nextLong();

        input.close();
        System.out.println(func(l, r));
    }

    public static int func(long l, long r) {
        // 使用埃拉托色尼筛法优化
        return countPrimesInRange(l, r);
    }

    public static int countPrimesInRange(long l, long r) {
        // 标记区间 [l, r] 中的数是否为素数
        boolean[] isPrime = new boolean[(int) (r - l + 1)];
        Arrays.fill(isPrime, true);

        // 从 2 开始筛选素数
        for (long i = 2; i * i <= r; i++) {
            long start = Math.max(i * i, (l + i - 1) / i * i); // 找到区间内第一个 i 的倍数
            for (long j = start; j <= r; j += i) {
                isPrime[(int) (j - l)] = false;
            }
        }

        // 特殊处理 1 不是素数
        if (l == 1) {
            isPrime[0] = false;
        }

        // 统计素数个数
        int count = 0;
        for (boolean prime : isPrime) {
            if (prime) {
                count++;
            }
        }
        return count;
    }
}

P1029 [NOIP 2001 普及组] 最大公约数和最小公倍数问题 

解题思路

  1. 理解条件

    • $P$$Q$ 的最大公约数是 $x_0$
    • $P$$Q$ 的最小公倍数是 $y_0$
    • 根据数学性质,$P \times Q = \text{gcd}(P, Q) \times \text{lcm}(P, Q)$,因此 $P \times Q = x_0 \times y_0$
  2. 优化计算

    • $z = y_0 / x_0$z 必须是整数。
    • 我们需要找到所有的正整数对 $(a, b)$,使得 $a \times b = z$$\text{gcd}(a, b) = 1$
    • 对于每个这样的 $(a, b)$,我们可以构造 $P = a \times x_0$$Q = b \times x_0$
  3. 实现代码

    • 遍历 $z$ 的所有因子,检查每对因子是否满足 $\text{gcd}(a, b) = 1$
    • 统计满足条件的因子对数量。

注意事项:

  1. 输入检查

    • 如果 $y_0$ 不是 $x_0$ 的倍数,则直接输出 0,因为无法满足条件。
    • 如果$x_0 = y_0$,直接返回1,两个数相等时只有一个结果就是它们本身。
  2. 因子遍历

    • 遍历 $z$ 的所有因子对 $(a, b)$,并检查 $\text{gcd}(a, b) = 1$
    • 每找到一个因子对 $(a, b)$,计数加 2,因为 $(a, b)$$(b, a)$ 都是有效解。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int x0 = input.nextInt();
        int y0 = input.nextInt();

        input.close();

        // 如果 y0 不是 x0 的倍数,则无解
        if (y0 % x0 != 0) {
            System.out.println(0);
            return;
        }

        if (x0 == y0) {
            System.out.println(1);
            return;
        }
        
        int z = y0 / x0; // z = y0 / x0
        int count = 0;

        // 遍历 z 的所有因子
        for (int a = 1; a * a <= z; a++) {
            if (z % a == 0) {
                int b = z / a; // 因子对 (a, b)

                // 检查 gcd(a, b) 是否为 1
                if (gcd(a, b) == 1) {
                    count += 2; // (a, b) 和 (b, a) 都满足条件
                }
            }
        }

        System.out.println(count);
    }
    
    // 辗转相除法计算 gcd
    private static int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
}

P1072 [NOIP 2009 提高组] Hankson 的趣味题

解题思路

  1. 枚举 $k$

    • $k$ 必须满足 $k \cdot a_1 \mid b_1$,因此 $k$ 的范围是 $b_1 / a_1$ 的所有因子。
  2. 验证条件

    • 对于每个 $k$,验证:
      • $\text{gcd}(k \cdot a_1, a_0) = a_1$
      • $\text{lcm}(k \cdot a_1, b_0) = b_1$
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt(); // 读取测试组数
        StringBuilder result = new StringBuilder();

        while (n-- > 0) {
            long a0 = input.nextLong();
            long a1 = input.nextLong();
            long b0 = input.nextLong();
            long b1 = input.nextLong();

            // 如果 a1 不是 a0 的因子,或者 b1 不是 b0 的倍数,则无解
            if (a0 % a1 != 0 || b1 % b0 != 0) {
                result.append(0).append("\n");
                continue;
            }

            // 枚举 k 的所有可能值
            long xBase = a1; // x = k * a1
            long limit = b1 / xBase; // k 的最大值
            int count = 0;

            for (long k = 1; k * k <= limit; k++) {
                if (limit % k == 0) {
                    // 检查两个因子 k 和 limit / k
                    if (isValid(k, xBase, a0, b0, b1)) count++;
                    if (k != limit / k && isValid(limit / k, xBase, a0, b0, b1)) count++;
                }
            }

            result.append(count).append("\n");
        }

        input.close();
        System.out.print(result);
    }

    // 检查 k 是否满足条件
    private static boolean isValid(long k, long xBase, long a0, long b0, long b1) {
        long x = k * xBase;
        return gcd(x, a0) == xBase && lcm(x, b0) == b1;
    }

    // 辗转相除法计算 gcd
    private static long gcd(long a, long b) {
        while (b != 0) {
            long temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    // 计算 lcm
    private static long lcm(long a, long b) {
        return a / gcd(a, b) * b;
    }
}

P1069 [NOIP 2009 普及组] 细胞分裂

解题思路

  1. 输入处理:读取细胞种数、试管参数和每个细胞的分裂数。

  2. 特殊情况处理:如果m_1为1,直接输出0,因为任何细胞都能立即满足条件。

  3. 质因数分解:将m_1分解质因数,并计算其在M = m_1^{m_2}中的指数。

  4. 细胞有效性检查:对于每个细胞的分裂数,检查是否包含M的所有质因数,并计算每个质因数的指数。

  5. 时间计算:针对每个有效细胞,计算满足条件所需的最长时间,并更新全局最短时间。

  6. 结果输出:根据是否存在有效细胞输出最短时间或-1。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int N = input.nextInt();
        int m1 = input.nextInt();
        int m2 = input.nextInt();
        int[] S = new int[N];
        for (int i = 0; i < N; i++) {
            S[i] = input.nextInt();
        }

        // 处理m1=1的特殊情况,任何情况都能在0秒完成
        if (m1 == 1) {
            System.out.println(0);
            return;
        }

        // 分解m1的质因数
        Map<Integer, Integer> m1Factors = primeFactors(m1);
        // 计算M的质因数分解(每个指数乘以m2)
        Map<Integer, Integer> mFactors = new HashMap<>();
        for (Map.Entry<Integer, Integer> entry : m1Factors.entrySet()) {
            mFactors.put(entry.getKey(), entry.getValue() * m2);
        }

        int minTime = Integer.MAX_VALUE;
        for (int s : S) {
            boolean valid = true;
            Map<Integer, Integer> sFactors = new HashMap<>();

            // 检查s是否包含所有M的质因数
            for (int p : mFactors.keySet()) {
                if (s % p != 0) {
                    valid = false;
                    break;
                }
                // 计算s中质因数p的指数
                int e = 0;
                int temp = s;
                while (temp % p == 0) {
                    e++;
                    temp /= p;
                }
                sFactors.put(p, e);
            }
            if (!valid)
                continue;

            // 计算当前s所需的最大时间
            int currentMax = 0;
            for (Map.Entry<Integer, Integer> entry : mFactors.entrySet()) {
                int p = entry.getKey();
                int requiredExp = entry.getValue(); // M中p的指数
                int sExp = sFactors.get(p); // s中p的指数

                // 向上取整计算所需时间
                int t = (requiredExp + sExp - 1) / sExp;
                if (t > currentMax) {
                    currentMax = t;
                }
            }

            // 更新全局最小时间
            if (currentMax < minTime) {
                minTime = currentMax;
            }
        }

        System.out.println(minTime == Integer.MAX_VALUE ? -1 : minTime);
        input.close();
    }

    // 质因数分解方法
    private static Map<Integer, Integer> primeFactors(int n) {
        Map<Integer, Integer> factors = new HashMap<>();
        if (n == 1)
            return factors;

        // 处理2的因数
        while (n % 2 == 0) {
            factors.put(2, factors.getOrDefault(2, 0) + 1);
            n /= 2;
        }

        // 处理奇数因数
        for (int i = 3; i * i <= n; i += 2) {
            while (n % i == 0) {
                factors.put(i, factors.getOrDefault(i, 0) + 1);
                n /= i;
            }
        }

        // 处理剩余的质因数
        if (n > 2) {
            factors.put(n, 1);
        }

        return factors;
    }
}

P1572 计算分数

解题思路

  1. 解析输入
    • 将输入的分数表达式拆分为多个分数项,每个分数项形如 a/b
    • 处理分数项之间的运算符(+ 或 -)。
  2. 分数运算
    • 使用分数的加减法公式:
      [ \frac{a}{b} + \frac{c}{d} = \frac{a \cdot d + c \cdot b}{b \cdot d} ] [ \frac{a}{b} - \frac{c}{d} = \frac{a \cdot d - c \cdot b}{b \cdot d} ]
    • 每次计算后化简分数(求最大公约数 GCD)。
  3. 化简分数
    • 使用欧几里得算法求最大公约数(GCD),将分子和分母同时除以 GCD。
  4. 输出结果
    • 如果分母为 1,则输出整数。
    • 否则输出最简分数。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String expression = input.nextLine();
        input.close();

        // 初始化分子和分母
        int numerator = 0; // 当前分子
        int denominator = 1; // 当前分母

        // 正则表达式匹配分数项
        String[] terms = expression.split("(?=[+-])"); // 按 `+` 或 `-` 分割

        for (String term : terms) {
            // 分割分子和分母
            String[] fraction = term.split("/");
            int num = Integer.parseInt(fraction[0]); // 分子
            int den = Integer.parseInt(fraction[1]); // 分母

            // 计算通分后的分子和分母
            numerator = numerator * den + num * denominator;
            denominator *= den;

            // 化简分数
            int gcd = gcd(Math.abs(numerator), denominator);
            numerator /= gcd;
            denominator /= gcd;
        }

        // 输出结果
        if (denominator == 1) {
            System.out.println(numerator); // 如果分母为 1,输出整数
        } else {
            System.out.println(numerator + "/" + denominator); // 输出最简分数
        }
    }

    // 求最大公约数(GCD)
    private static int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
}

P4057 [Code+#1] 晨跑

解题思路

  1. 问题分析
    • 三位同学的晨跑周期分别为 ab 和 c
    • 他们在第 0 天相遇,下一次相遇的天数是 ab 和 c 的最小公倍数(LCM)。
  2. 计算最小公倍数(LCM)
    • 最小公倍数公式:
      [ \text{LCM}(x, y) = \frac{x \cdot y}{\text{GCD}(x, y)} ]
    • 其中 GCD 是最大公约数,可以使用欧几里得算法计算。
  3. 扩展到三个数
    • 先计算两个数的 LCM,然后将结果与第三个数计算 LCM。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 读取输入
        long a = input.nextLong();
        long b = input.nextLong();
        long c = input.nextLong();
        input.close();

        // 计算三者的最小公倍数
        long lcmAB = lcm(a, b); // a 和 b 的最小公倍数
        long lcmABC = lcm(lcmAB, c); // lcmAB 和 c 的最小公倍数

        // 输出结果
        System.out.println(lcmABC);
    }

    // 求最大公约数(GCD)
    private static long gcd(long x, long y) {
        return y == 0 ? x : gcd(y, x % y);
    }

    // 求最小公倍数(LCM)
    private static long lcm(long x, long y) {
        return x / gcd(x, y) * y; // 防止溢出,先除后乘
    }
}

P1414 又是毕业季II

解题思路

  1. 输入处理与频率统计
    读取输入数据并统计每个数出现的次数,记录在freq数组中,同时确定最大值max_x

  2. 因数统计
    对每个存在的数生成其所有因数,并统计每个因数的倍数总次数cnt[d]。例如,若数x出现freq[x]次,则其所有因数d对应的cnt[d]增加freq[x]

  3. 因数降序处理
    将所有可能的因数按降序排列,确保后续处理时优先考虑较大的因数。

  4. 填充答案数组
    遍历降序后的因数列表,根据每个因数d的倍数总数cnt[d],更新答案数组ans。对于每个d,若其倍数总数c大于当前已覆盖的最大值max_so_far,则填充ans数组的未覆盖区间,确保每个k取到最大可能的d

  5. 输出结果
    按顺序输出ans[1]ans[n],即每个k对应的最大公约数。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int[] a = new int[n];
        int max_x = 0;
        int[] freq = new int[1000002]; // 存储每个数的出现次数,最大1e6+1

        // 读取输入并统计频率
        for (int i = 0; i < n; i++) {
            a[i] = input.nextInt();
            freq[a[i]]++;
            if (a[i] > max_x) {
                max_x = a[i];
            }
        }

        int[] cnt = new int[max_x + 2]; // cnt[d]表示d的倍数的总个数
        Set<Integer> divisorsSet = new HashSet<>();

        // 统计所有因数及其出现次数
        for (int x = 1; x <= max_x; x++) {
            if (freq[x] == 0)
                continue;
            List<Integer> divisors = getDivisors(x);
            for (int d : divisors) {
                cnt[d] += freq[x];
                divisorsSet.add(d);
            }
        }

        // 将因数按降序排列
        List<Integer> divisorsList = new ArrayList<>(divisorsSet);
        Collections.sort(divisorsList, Collections.reverseOrder());

        int[] ans = new int[n + 1]; // ans[k]表示选k人时的最大gcd
        int max_so_far = 0;

        // 填充答案数组
        for (int d : divisorsList) {
            int c = cnt[d];
            if (c == 0)
                continue;
            if (c > max_so_far) {
                int start = max_so_far + 1;
                int end = Math.min(c, n);
                if (start > end)
                    continue;
                for (int k = start; k <= end; k++) {
                    ans[k] = d;
                }
                max_so_far = end;
                if (max_so_far == n)
                    break; // 提前终止
            }
        }

        // 输出结果
        for (int k = 1; k <= n; k++) {
            System.out.println(ans[k]);
        }
        input.close();
    }

    // 生成一个数的所有因数
    private static List<Integer> getDivisors(int x) {
        List<Integer> divisors = new ArrayList<>();
        for (int i = 1; i * i <= x; i++) {
            if (x % i == 0) {
                divisors.add(i);
                int other = x / i;
                if (other != i) {
                    divisors.add(other);
                }
            }
        }
        return divisors;
    }
}

P2651 添加括号III

解题思路

  1. 输入处理:使用Scanner读取输入数据,处理多个测试用例。

  2. 特殊情况处理:当分母为1时,直接输出"Yes"。

  3. 质因数分解函数:对给定的数进行质因数分解,返回质因数及其指数的映射。

  4. 统计质因数次数:遍历分子部分的每个数,统计所有质因数的总次数。

  5. 结果判断:检查分母的每个质因数是否在分子中有足够的次数覆盖,若全部满足则输出"Yes",否则输出"No"。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int t = input.nextInt();
        while (t-- > 0) {
            int n = input.nextInt();
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = input.nextInt();
            }
            if (n < 2) {
                System.out.println("Yes");
                continue;
            }
            int a2 = a[1];
            if (a2 == 1) {
                System.out.println("Yes");
                continue;
            }

            // 计算第二个元素的质因数分解结果
            Map<Integer, Integer> m2Factors = primeFactors(a2);
            // 如果没有质因数,输出yes跳到下一个元素
            if (m2Factors.isEmpty()) {
                System.out.println("Yes");
                continue;
            }
            int a1 = a[0];
            // 计算第一个元素的质因数分解结果
            Map<Integer, Integer> total = primeFactors(a1);
            // 从数组的第三个元素开始,依次计算每个元素的质因数分解结果
            for (int i = 2; i < n; i++) {
                int x = a[i];
                // 计算当前元素的质因数分解结果
                Map<Integer, Integer> xFactors = primeFactors(x);
                // 遍历当前元素的质因数分解结果
                for (Map.Entry<Integer, Integer> entry : xFactors.entrySet()) {
                    // 获取质因数
                    int p = entry.getKey();
                    // 获取该质因数的个数
                    int cnt = entry.getValue();
                    // 将该质因数的个数累加到 total 中
                    total.put(p, total.getOrDefault(p, 0) + cnt);
                }
            }
            boolean ok = true;
            // 遍历第二个元素的质因数分解结果
            for (Map.Entry<Integer, Integer> entry : m2Factors.entrySet()) {
                int p = entry.getKey();
                int required = entry.getValue();
                int actual = total.getOrDefault(p, 0);
                if (actual < required) {
                    ok = false;
                    break;
                }
            }
            System.out.println(ok ? "Yes" : "No");
        }
        input.close();
    }

    // 计算一个数的质因数分解结果
    private static Map<Integer, Integer> primeFactors(int x) {
        // 用于存储质因数及其个数的映射
        Map<Integer, Integer> factors = new HashMap<>();
        if (x == 1) {
            return factors;
        }
        // 处理 2 这个质因数
        while (x % 2 == 0) {
            factors.put(2, factors.getOrDefault(2, 0) + 1);
            x /= 2;
        }
        // 从 3 开始,以 2 为步长遍历奇数,直到 sqrt(x)
        for (int i = 3; i <= Math.sqrt(x); i += 2) {
            while (x % i == 0) {
                // 更新当前质因数的个数
                factors.put(i, factors.getOrDefault(i, 0) + 1);
                // 将 x 除以当前质因数
                x /= i;
            }
        }
        // 如果 x 大于 2,说明 x 本身是一个质数,将其作为质因数添加到映射中
        if (x > 2) {
            factors.put(x, 1);
        }
        return factors;
    }
}

P2660 zzc 种田

解题思路

  1. 输入处理:使用 Scanner 读取输入的 x 和 y 值。

  2. 计算最大公约数:通过欧几里得算法计算 x 和 y 的最大公约数。

  3. 公式计算:代入公式 4×(x+y−gcd(x,y))4×(x+y−gcd(x,y)) 得到最小体力值,并输出结果。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        long x = input.nextLong();
        long y = input.nextLong();
        long gcd = gcd(x, y);
        System.out.println(4 * (x + y - gcd));
        input.close();
    }

    private static long gcd(long a, long b) {
        while (b != 0) {
            long temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
}

P3601 签到题

解题思路

  1. 质因数分解与欧拉函数:欧拉函数 \varphi (x) 表示与 x 互质的数的个数。通过分解 x 的质因数并应用欧拉函数公式 \varphi (x) =x*\sqcap (1-\frac{1}{p}),其中 p 是 x 的质因数,我们可以高效计算\varphi (x)

  2. 筛法生成质数:使用埃拉托斯特尼筛法生成所有小于等于 sqrt(r) 的质数,用于后续处理。

  3. 区间处理:对于每个质数 p,处理区间中的每个 p 的倍数,计算对应的欧拉函数值。

  4. 剩余质数处理:处理区间中剩余的大质数,确保每个数的欧拉函数正确计算。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        long l = input.nextLong();
        long r = input.nextLong();
        int len = (int) (r - l + 1);
        long[] phi = new long[len];
        long[] rem = new long[len];
        for (int i = 0; i < len; i++) {
            long x = l + i;
            phi[i] = x;
            rem[i] = x;
        }

        int maxPrime = (int) Math.sqrt(r) + 1;
        boolean[] isPrime = new boolean[maxPrime + 1];
        Arrays.fill(isPrime, true);     // 假设所有数都是素数
        isPrime[0] = isPrime[1] = false;    // 0 和 1 不是素数

        // 使用埃拉托色尼筛法标记非素数
        for (int i = 2; i * i <= maxPrime; i++) {
            if (isPrime[i]) {
                for (int j = i * i; j <= maxPrime; j += i) {
                    isPrime[j] = false;     // 标记为非素数
                }
            }
        }

        List<Integer> primes = new ArrayList<>();
        for (int i = 2; i <= maxPrime; i++) {
            if (isPrime[i]) {
                primes.add(i);
            }
        }

        // 遍历每个素数,对区间 [l, r] 内的数进行处理
        for (int p : primes) {
            // 找到区间内第一个是 p 的倍数的数
            long start = ((l + p - 1) / p) * p;
            if (start > r) {
                continue; // 如果起始点超出区间,跳过
            }
            // 遍历区间内所有是 p 的倍数的数
            for (long x = start; x <= r; x += p) {
                int i = (int) (x - l); // 计算当前数在数组中的索引
                if (rem[i] < p) {
                    continue; // 如果余数小于当前素数,跳过
                }
                // 如果当前数能被 p 整除,更新 phi 和 rem
                if (rem[i] % p == 0) {
                    phi[i] = phi[i] / p * (p - 1); // 更新欧拉函数值
                    while (rem[i] % p == 0) {
                        rem[i] /= p;
                    }
                }
            }
        }

        // 处理区间内剩余的数(大于 sqrt(r) 的素因子)
        for (int i = 0; i < len; i++) {
            if (rem[i] > 1) {   // 如果余数大于 1,说明是一个素数
                phi[i] = phi[i] / rem[i] * (rem[i] - 1);    // 更新欧拉函数值
            }
        }

        long sum = 0;
        long mod = 666623333;
        for (int i = 0; i < len; i++) {
            long current = ((l + i) - phi[i]) % mod;    // 计算当前数的结果
            sum = (sum + current) % mod;    // 累加结果
        }
        System.out.println(sum);
        input.close();
    }
}

P1403 [AHOI2005] 约数研究

解题思路

  1. 约数个数的性质

    • 对于一个数 i,它的约数可以通过遍历 1 到 sqrt(i) 来找到。
    • 但是直接计算每个数的约数个数会导致时间复杂度为 O(n * \sqrt n),对于 n 最大为 10^6 的情况,这种方法会超时。
  2. 优化方法

    • 利用约数的性质,反向思考:对于每个数 j,它会作为约数出现在 j, 2j, 3j, ... 中。
    • 因此,可以通过遍历每个数 j,将其作为约数累加到所有的倍数上。
    • 这种方法的时间复杂度为 O(n),适合 n 较大的情况。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        input.close();

        System.out.println(sumOfDivisors(n));
    }

    // 计算从 1 到 n 的所有正整数的约数个数之和
    public static long sumOfDivisors(int n) {
        long sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += (long) (n / i);
        }
        return sum;
    }
}

P1593 因子和

解题思路

  1. ​质因数分解​​:

    • 将输入的数 a 分解为质因数的乘积形式,例如 a=p_1^{e_1}\times p_2^{e_2}\times \dots \times p_n^{e_n}
    • 对于每个质因数 p_i,在 a^b 中的指数变为 e_i\times b
  2. ​等比数列求和​​:

    • 对于每个质因数 p 和其指数 e×b,计算等比数列 1+p+p^2+\dots+p^e 的和。
    • 使用公式 (p^{e+1}-1)/(p-1) 计算和,但需要注意模运算中的除法处理。
  3. ​模逆元计算​​:

    • 在模运算中,除法转换为乘以模逆元。利用费马小定理(因为 9901 是质数),模逆元可通过快速幂计算。
  4. ​特殊情况处理​​:

    • 当 p≡1 mod 9901 时,分母为 0,此时等比数列的和简化为 e+1。
    • 处理 a=0 或 b=0 的边界情况。
import java.util.*;

public class Main {
    static final int MOD = 9901;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int a = input.nextInt();
        int b = input.nextInt();
        input.close();

        System.out.println(factorSum(a, b));
    }

    // 计算a^b的因子和,对MOD取模
    public static int factorSum(int a, int b) {
        if (b == 0) return 1; // a^0 = 1,因子和为1
        if (a == 0) return 0; // 0^b = 0(b>0),因子和为0

        Map<Integer, Integer> primeFactors = primeFactorize(a);
        int result = 1;

        for (Map.Entry<Integer, Integer> entry : primeFactors.entrySet()) {
            int p = entry.getKey();
            int e = entry.getValue() * b;
            result = (result * geometricSum(p, e)) % MOD;
        }

        return (result % MOD + MOD) % MOD; // 确保非负
    }

    // 质因数分解
    public static Map<Integer, Integer> primeFactorize(int n) {
        Map<Integer, Integer> factors = new HashMap<>();
        // 处理偶数
        if (n % 2 == 0) {
            int count = 0;
            while (n % 2 == 0) {
                count++;
                n /= 2;
            }
            factors.put(2, count);
        }
        // 处理奇数
        for (int i = 3; (long) i * i <= n; i += 2) {
            if (n % i == 0) {
                int count = 0;
                while (n % i == 0) {
                    count++;
                    n /= i;
                }
                factors.put(i, count);
            }
        }
        // 处理剩余的质数
        if (n > 1) {
            factors.put(n, 1);
        }
        return factors;
    }

    // 计算等比数列和 (1 + p + p^2 + ... + p^e) % MOD
    public static int geometricSum(int p, int e) {
        if (e == 0) return 1; // 指数为0时和为1

        long pMod = p % MOD;
        if (pMod == 1) { // 当p ≡ 1 (mod MOD)时,和为e+1
            return (e + 1) % MOD;
        }

        // 计算 (p^(e+1) - 1) / (p-1) mod MOD
        long numerator = (modPow(p, e + 1, MOD) - 1 + MOD) % MOD; // 分子,处理负数
        int denominator = modInverse((int) ((p - 1) % MOD), MOD); // 分母的逆元
        return (int) ((numerator * denominator) % MOD);
    }

    // 快速幂计算 (base^exp) % mod
    public static int modPow(int base, int exp, int mod) {
        long result = 1;
        long x = base % mod;
        while (exp > 0) {
            if ((exp & 1) == 1) {
                result = (result * x) % mod;
            }
            x = (x * x) % mod;
            exp >>= 1;
        }
        return (int) result;
    }

    // 费马小定理求逆元:a^(mod-2) % mod(要求mod为质数)
    public static int modInverse(int a, int mod) {
        return modPow(a, mod - 2, mod);
    }
}

相关文章:

  • 一款基于 .NET 8 + Vue 开源的、企业级中后台权限管理系统
  • 设计模式 Day 6:深入讲透观察者模式(真实场景 + 回调机制 + 高级理解)
  • Every ending plants the seed for a new beginning.
  • 【记录:重装win11内存占用高的问题】
  • React 第三十节 使用 useState 和 useEffect Hook实现购物车
  • 蓝桥杯真题:英文字母
  • PHP 阿里云oss 使用指南
  • 网络2 IP与MAC IP地址
  • HTML的svg元素
  • jupyter notebook 显示conda虚拟环境
  • LLM生成文本的 束搜索参数是什么(Beam Search)
  • Quarkus 2025终极指南:GraalVM Native Image如何让Java在K8s中起飞?
  • 在 Jupyter Notebook 中使用 Pandas 进行数据操作
  • [实战]多天线空域抗干扰技术:原理、数学推导与工程仿真(完整仿真代码)
  • pytorch 反向传播
  • 力扣刷题DAY11(动态规划-线性DP)
  • 设计模式 --- 状态模式
  • 【软件测试】Postman中如何搭建Mock服务
  • Java流程控制【if分支三种形式】
  • 前端知识点---垃圾回收机制(javascript)
  • 网络营销的主要形式/网络优化工程师主要负责什么工作
  • 做网站哪个公司最/武安百度seo
  • 网站做浮动边框asp代码/360网站安全检测
  • 保定哪家做网站公司好/磁力搜索器在线
  • 东莞电商网页设计/qq群排名优化软件购买
  • 哪些网站可以做招生/网上营销方式和方法