java 洛谷题单【算法1-3】暴力枚举

 P2241 统计方形(数据加强版)

解题思路

对于 i=j ,每个 (n−i)×(m−j)项表示从 (i,j)到右下角的子矩形个数(即正方形个数)。

对于 i≠j,每个 (n−i)×(m−j) 项表示从 (i,j)到右下角的子矩形个数(即长方形个数)。

import java.util.Scanner;

/**
 * @Author HeShen.
 * @Date 2024/4/13 19:49
 */

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

        int n = input.nextInt();
        int m = input.nextInt();
        long sq = 0;
        long re = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (i == j) {
                    sq += (long) (n - i) *  (m - j);
                }else {
                    re += (long) (n - i) *  (m - j);
                }
            }
        }
        System.out.println(sq + " " + re);
    }
}

P2089 烤鸡

解题思路

我们将使用递归回溯方法来探索所有可能的调味料组合。

  •  n表示美味程度。
  • count 统计有效组合的总数。
  • res 存储所有有效组合。
  • Recursive Function:

    • 将每种调味料从 1 次迭代到 10 次。
    • 每种调味料的用量尝试为 1 至 3 克。
    • 使用更新后的总和递归尝试下一个调味料。
    • 如果组合有效,则存储它。
  • Output the Results:输出结果:

    • 首先,打印有效组合的总数。
    • 然后,按字典顺序打印每个组合。
import java.util.*;

/**
 * @Author HeShen.
 * @Date 2024/4/13 19:49
 */

public class Main {
    static int count = 0;
    static List<int[]> res = new ArrayList<>();
    static int[] ans = new int[11];
    
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        int n = input.nextInt();
        // 调用回溯函数
        func(1, 0, n);
        
        System.out.println(count);

        // 对res进行排序
        res.sort((a, b) -> {
            for (int i = 0; i < 10; i++) {
                if (a[i] != b[i]) {
                    return a[i] - b[i];
                }
            }
            return 0;
        });
        // 输出res中的元素
        for (int[] res : res)  {
            for (int i = 0; i < 10; i++) {
                System.out.print(res[i] + " ");
            }
            System.out.println();
        }
    }
    
    public static void func(int index,int m, int n) {
        // 如果index大于10,说明已经遍历完,则进行以下操作
        if (index > 10) {
            // 如果m等于n,说明已经达到n的美味程度,则进行以下操作
            if (m == n) {
                // 将count的值加1
                count++;
                // 将ans的值添加到res中
                res.add(Arrays.copyOfRange(ans, 1, 11));
            }
            return;
        }
        
        for (int i = 1; i < 4; i++) {
            // 遍历1到3,如果m加上i大于n,说明美味值超标,不符合要求,则跳出循环
            if (m + i > n) break;
            // 将i的值赋给ans[index]
            ans[index] = i;
            // 调用func函数,index加1,m加上i,遍历下一个调料
            func(index + 1, m + i, n);
            // 将ans[index]的值赋为0,回归初始状态
            ans[index] = 0;
        }
    }
}

P1618 三连击(升级版)

解题思路

要将1到9的数字分成三组,并使得这三个三位数的比例为给定的A:B:C,我们需要进行如下步骤:

  1. 生成所有可能的三位数组合:将1到9的所有排列组合生成可能的三位数组合。

  2. 检查比例条件:对于每一组三位数,检查它们是否满足比例A:B:C。

  3. 输出结果:若满足条件的组合存在,则输出这些组合,否则输出 "No!!!"。

具体步骤如下:

  1. 生成所有可能的三位数组合

    • permute方法生成所有1到9的排列,并在生成排列的过程中,检查每一个排列是否满足条件。
  2. 检查比例条件

    • 从排列中提取出三个三位数x, y, z。
    • 检查y * A == x * Bz * B == y * C
  3. 输出结果

    • 满足条件的组合存入results列表。
    • 使用Collections.sort对结果按第一个三位数排序。
    • 如果结果列表为空,输出"No!!!"
    • 否则,按要求格式输出所有满足条件的组合。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int A = scanner.nextInt();
        int B = scanner.nextInt();
        int C = scanner.nextInt();
        scanner.close();

        // 创建一个结果列表
        List<int[]> results = new ArrayList<>();
        // 创建一个字符串,包含0-9的字符
        String digits = "123456789";

        // 调用排列函数,将字符串中的字符排列,并传入结果列表和ABC
        permute(digits.toCharArray(), 0, digits.length(), results, A, B, C);

        // 对结果列表进行排序
        results.sort(Comparator.comparingInt(o -> o[0]));

        // 如果结果列表为空,则输出No!!!
        if (results.isEmpty()) {
            System.out.println("No!!!");
        } else {
            // 否则,输出结果列表中的每个数组
            for (int[] result : results) {
                System.out.println(result[0] + " " + result[1] + " " + result[2]);
            }
        }
    }

    private static void permute(char[] arr, int l, int r, List<int[]> results, int A, int B, int C) {
        // 如果l等于r,则将字符数组中的字符拼接成三个字符串,并转换成整数
        if (l == r) {
            int x = Integer.parseInt(new String(arr, 0, 3));
            int y = Integer.parseInt(new String(arr, 3, 3));
            int z = Integer.parseInt(new String(arr, 6, 3));

            // 如果y乘以A等于x乘以B,并且z乘以B等于y乘以C,则将结果添加到结果列表中
            if (y * A == x * B && z * B == y * C) {
                results.add(new int[] { x, y, z });
            }
        } else {
            // 否则,遍历字符数组,将字符数组中的字符进行交换,并递归调用排列函数
            for (int i = l; i < r; i++) {
                swap(arr, l, i);
                permute(arr, l + 1, r, results, A, B, C);
                swap(arr, l, i);
            }
        }
    }

    private static void swap(char[] arr, int i, int j) {
        // 交换字符数组中的两个字符
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

P1036 [NOIP2002 普及组] 选数

解题思路

我们需要从给定的n个整数中任选k个整数相加,计算这些组合的和,并统计其中有多少种和是素数。这个问题可以分解为以下几个步骤:

  1. 生成所有从n个数中选择k个数的组合。
  2. 计算每个组合的和。
  3. 检查每个和是否为素数。
  4. 统计并输出素数的个数。

generateCombination函数的解释:

  • int[] arr: 原始数组,包含所有可选元素。
  • int n: 数组的长度。
  • int k: 需要选择的元素个数。
  • int index: 当前处理的数组索引。
  • List<Integer> current: 当前正在构建的组合。
  • List<List<Integer>> combinations: 存储所有生成的组合的列表。
if (current.size() == k) {
            combination.add(new ArrayList<>(current));
            return;
        }
  • 检查current组合的大小是否已经达到k
  • 如果是,说明当前组合已经完整,将其加入combinations列表。
  • 使用new ArrayList<>(current)创建一个current的副本,以防止后续修改影响已保存的组合。
  • 然后返回,结束当前递归。
    if (index == n) {
        return;
    }
  • 检查当前处理的索引是否等于数组长度n
  • 如果是,说明已经处理完数组中的所有元素,返回,结束当前递归。

 选择当前元素:

    current.add(arr[index]);
    generateCombinations(arr, n, k, index + 1, current, combinations);
    current.remove(current.size() - 1);
  • current.add(arr[index]): 将当前索引index对应的元素arr[index]添加到当前组合current中。
  • generateCombinations(arr, n, k, index + 1, current, combinations): 递归调用函数,将索引增加1,处理下一个元素。
  • current.remove(current.size() - 1): 移除刚才添加的元素,恢复到之前的状态,准备处理不选择当前元素的情况。

 不选择当前元素:

    generateCombinations(arr, n, k, index + 1, current, combinations);

递归调用函数,将索引增加1,处理下一个元素,但不选择当前元素。

求和语句的解释  

int sum = list.stream().mapToInt(Integer::intValue).sum();
  1. list.stream(): 将列表转换为流(Stream)。
  2. mapToInt(Integer::intValue): 对流中的每个元素执行一个函数,将元素转换为整数。这里使用的是Integer::intValue方法,它将每个元素转换为其整数值。
  3. sum(): 对流中的整数进行求和操作。
  4. int sum = ...;: 将求和结果存储在变量sum中。
import java.util.*;

/**
 * @Author HeShen.
 * @Date 2024/4/13 19:49
 */

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

        int n = input.nextInt();
        int k = input.nextInt();
        int count = 0;
        int[] arr = new int[n];

        for (int i = 0; i < n; i++) {
            arr[i] = input.nextInt();
        }

        // 存储n个数中选择k个数的组合
        List<List<Integer>> combination = new ArrayList<>();

        //递归调用生成所有的组合
        generateCombination(arr, n, k, 0, new ArrayList<>(), combination);

        for (List<Integer> list : combination) {
            // 计算每个组合的和
            int sum = list.stream().mapToInt(Integer::intValue).sum();
            if (isPrime(sum)) {
                count++;
            }
        }
        System.out.println(count);
    }

    // 从n个数中选择k个数的组合
    private static void generateCombination(int[] arr, int n, int k, int index, ArrayList<Integer> current, List<List<Integer>> combination) {
        if (current.size() == k) {
            combination.add(new ArrayList<>(current));
            return;
        }
        if (index == n) {
            return;
        }
        current.add(arr[index]);
        generateCombination(arr, n, k, index + 1, current, combination);
        current.remove(current.size() - 1);
        generateCombination(arr, n, k, index + 1, current, combination);
    }

    // 判断素数
    public static boolean isPrime(int num) {
        if (num <= 3) return num > 1;
        if (num % 6 != 1 && num % 6 != 5){
            return false;
        }
        int sqrt = (int) Math.sqrt(num);
        for (int i = 5; i <= sqrt; i += 6) {
            if (num % i == 0 || num % (i + 2) == 0){
                return false;
            }
        }
        return true;
    }
}

P1157 组合的输出

解题思路

  • 理解题目要求

    • 输入两个自然数 nr,其中 1 < n < 210 ≤ r ≤ n。(可以打表)
    • 需要从 1n 中选择 r 个数,生成所有可能的组合。
    • 每个组合中的数字按升序排列。
    • 输出时,每个数字占据3个字符宽度,并且所有组合按字典序排列。
  • 生成组合

    • 组合的特点是无序的,即 {1, 2, 3}{3, 2, 1} 被视为同一组合。
    • 可以使用递归或者迭代的方法生成组合。
    • 递归方法更容易理解和实现,特别是在生成固定长度的组合时。
  • 递归方法

    • 递归生成组合时,首先选定一个数字,然后从剩余数字中继续选择。
    • 终止条件是当前组合长度达到 r
    • 为了确保组合按字典序排列,每次选择数字从当前数字的下一个数字开始选择。
  • 格式化输出

    • 输出每个数字时,需要确保其占3个字符宽度,可以使用 String.formatSystem.out.printf
import java.util.*;


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

        // 存储所有组合
        List<int[]> combinations = new ArrayList<>();
        func(combinations, new int[r], 0, 1, n);

        combinations.sort((a, b) -> {
            for (int i = 0; i < a.length; i++) {
                if (a[i] != b[i]) {
                    return Integer.compare(a[i], b[i]);
                }
            }
            return 0;
        });

        for (int[] c : combinations) {
            for (int i : c) {
                System.out.printf("%3d", i);
            }
            System.out.println();
        }
    }

    /**
     * 递归生成所有从start到end中选择r个数的组合
     * @param combinations 存储所有组合的列表
     * @param num 当前组合的数组,长度为r
     * @param index 当前数组的位置
     * @param start 起始位置
     * @param end 结束位置
     */
    public static void func(List<int[]> combinations, int[] num, int index, int start, int end)  {
        // 递归终止条件:已经选择了r个数
        if (index == num.length) {
            combinations.add(num.clone());
            return;
        }

        // 从start到end选择数字,并递归生成后续组合
        for (int i = start; i <= end && end - i + 1 >= num.length - index; i++) {
            num[index] = i;
            func(combinations, num, index + 1, i + 1, end);
        }
    }
}

P1706 全排列问题

解题思路

参考上题

  • 递归方法

    • 递归生成全排列,每个数字不重复。

这段代码呢只有80分,示例三:当n为8时,空间内存超出。

可以参考这个类型的写法。

import java.util.*;

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

        List<List<Integer>> combinations = new ArrayList<>();
        List<Integer> nums = new ArrayList<>();

        for (int i = 1; i <= n; i++) {
            nums.add(i);
        }

        func(combinations, nums, 0);

        // 按每个组合中元素的大小进行排序
        combinations.sort((a, b) -> {
            for (int i = 0; i < a.size(); i++) {
                if (!a.get(i).equals(b.get(i))) {
                    return a.get(i) - b.get(i);
                }
            }
            return 0;
        });

        for (List<Integer> c : combinations) {
            for (Integer num : c) {
                System.out.printf("%5d", num);
            }
            System.out.println();
        }
    }

    /**
     * 递归生成所有从start到end中选择r个数的组合
     * @param combinations 存储所有组合的列表
     * @param nums 当前排列的列表
     * @param index 当前排列的起始位置
     */
    private static void func(List<List<Integer>> combinations, List<Integer> nums, int index) {
        // 递归终止条件
        if (index == nums.size()) {
            combinations.add(new ArrayList<>(nums));
            return;
        }

        for (int i = index; i < nums.size(); i++) {
            // 交换当前位置和index位置的元素,产生新的排列
            Collections.swap(nums, index, i);
            // 递归调用,继续生成新的排列
            func(combinations, nums, index + 1);
            // 恢复当前位置和index位置的元素,恢复到原始排列
            Collections.swap(nums, index, i);
        }
    }
}

 AC代码

generatePermutations 方法

  • 用于递归生成排列。
  • 递归基条件:当深度等于数字总数时,表示生成了一个完整的排列,调用 printPermutation 方法输出该排列。
  • 通过循环和回溯方法,依次尝试每个数字,生成排列。
import java.util.Scanner;

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

        // 初始化数组 numbers 存储 1 到 n 的数字
        int[] numbers = new int[n];
        for (int i = 0; i < n; i++) {
            numbers[i] = i + 1;
        }

        // 初始化布尔数组 used 跟踪哪些数字已被使用
        boolean[] used = new boolean[n];
        // 初始化数组 result 用于存储当前排列
        int[] result = new int[n];

        // 调用递归方法生成排列并直接输出
        generatePermutations(numbers, used, result, 0, n);
    }

    /**
     * 递归生成排列并直接输出
     * @param numbers 包含所有待排列的数字
     * @param used 用于跟踪哪些数字已被使用
     * @param result 用于存储当前排列
     * @param depth 当前递归深度(排列中的位置)
     * @param n 数字的总数
     */
    private static void generatePermutations(int[] numbers, boolean[] used, int[] result, int depth, int n) {
        // 如果当前深度等于数字总数,表示生成了一个完整的排列
        if (depth == n) {
            printPermutation(result);
            return;
        }

        // 遍历所有数字,尝试生成排列
        for (int i = 0; i < n; i++) {
            // 如果当前数字尚未被使用
            if (!used[i]) {
                // 标记当前数字为已使用
                used[i] = true;
                // 将当前数字放入排列的当前深度位置
                result[depth] = numbers[i];
                // 递归生成下一个深度的排列
                generatePermutations(numbers, used, result, depth + 1, n);
                // 回溯:将当前数字标记为未使用
                used[i] = false;
            }
        }
    }

    /**
     * 输出一个排列
     * @param permutation 要输出的排列
     */
    private static void printPermutation(int[] permutation) {
        // 遍历排列中的每个数字
        for (int num : permutation) {
            // 按宽度为5的格式输出每个数字
            System.out.printf("%5d", num);
        }
        // 输出换行符
        System.out.println();
    }
}

P1088 [NOIP2004 普及组] 火星人

解题思路

  • 处理初始排列:

    • 创建一个布尔数组 used,用于标记哪些数已经被使用过。
    • 遍历输入的排列,将排列的每个数转换为相应的索引位置。
    • 计算当前数相对于未使用的数的偏移量,并更新 a 数组。
  • 增加 m 并处理进位:

    • 在排列末尾增加 m
    • 从后向前处理进位,将 a[i] 中的进位部分加到 a[i-1]
  • 生成新排列:

    • 使用布尔数组 used 跟踪已经使用的元素。
    • 调整元素,确保输出的排列正确。
    • 输出新的排列,元素间用空格隔开。
import java.util.Scanner;

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

        // 读取输入
        int n = input.nextInt();
        int m = input.nextInt();
        // 存储输入的整数
        int[] a = new int[n + 1];
        // 标记输入的整数是否使用过
        boolean[] used = new boolean[n + 1];

        // 遍历输入的n个整数,将它们存入数组a中,并计算出每个整数中1的个数
        for (int i = 1; i <= n; i++) {
            a[i] = input.nextInt();
            int x = a[i];
            for (int j = 1; j <= a[i]; j++) {
                x -= used[j] ? 1 : 0;
            }
            used[a[i]] = true;
            a[i] = x - 1;
        }

        // 计算出每个整数中1的个数,并将它们存入数组a中
        a[n] += m;
        for (int i = n; i > 0; i--) {
            a[i - 1] += a[i] / (n - i + 1);
            a[i] %= (n - i + 1);
        }

        // 初始化used数组
        used = new boolean[n + 1];

        // 遍历数组a,计算出每个整数中1的个数,并将它们存入used数组中
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= a[i]; j++) {
                if (used[j]) {
                    a[i]++;
                }
            }
            System.out.print((a[i] + 1) + " ");
            used[a[i]] = true;
        }
    }
}

P3392 涂条纹 

解题思路

要解决这个问题,我们需要找到一种方式将一个由 N × M 个小方块组成的棋盘改造成合法图案,要求改变的格子数量最少。合法图案的定义是:

  1. 最上方若干行(至少一行)全是白色的。
  2. 接下来若干行(至少一行)全是蓝色的。
  3. 剩下的行(至少一行)全是红色的。

可以用动态规划的思想来求解这个问题。具体步骤如下:

  1. 定义状态

    • 用三个数组 whiteCost[i], blueCost[i]redCost[i] 分别表示将第 i 行全部变成白色、蓝色或红色所需要的代价。
    • 使用一个三维数组 dp[i][j][k] 表示前 i 行中,第 j 行及之前的所有行都涂成白色,第 k 行及之后的所有行都涂成红色的最小代价。
  2. 初始化

    • 计算出每一行变成白色、蓝色、红色的代价。
  3. 状态转移

    • 遍历所有可能的分割点,将前面部分变为白色,中间部分变为蓝色,后面部分变为红色,并记录最小代价。
  4. 求解

    • 遍历所有可能的分割方式,计算最终的最小代价。
import java.util.Scanner;

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

        int N = input.nextInt();
        int M = input.nextInt();

        char[][] flag = new char[N][M];
        for (int i = 0; i < N; i++) {
            flag[i] = input.nextLine().toCharArray();
        }

        int[] whiteCost = new int[N];
        int[] blueCost = new int[N];
        int[] redCost = new int[N];

        // 计算将每行更改为白色、蓝色和红色的成本
        for (int i = 0; i < N; i++) {
            int wCost = 0, bCost = 0, rCost = 0;
            for (int j = 0; j < M; j++) {
                if (flag[i][j] != 'W') wCost++;
                if (flag[i][j] != 'B') bCost++;
                if (flag[i][j] != 'R') rCost++;
            }
            whiteCost[i] = wCost;
            blueCost[i] = bCost;
            redCost[i] = rCost;
        }

        // 使标志有效的最小成本
        int minCost = Integer.MAX_VALUE;

        // 将所有可能的划分迭代为三个部分
        for (int wEnd = 0; wEnd < N - 2; wEnd++) {
            for (int bEnd = wEnd + 1; bEnd < N - 1; bEnd++) {
                int cost = 0;

                // 将第0行到第end行涂成白色的成本
                for (int i = 0; i <= wEnd; i++) {
                    cost += whiteCost[i];
                }

                // 将行(wEnd + 1)涂成蓝色的成本
                for (int i = wEnd + 1; i <= bEnd; i++) {
                    cost += blueCost[i];
                }

                // 将(bEnd + 1)至(N - 1)行涂成红色的成本
                for (int i = bEnd + 1; i < N; i++) {
                    cost += redCost[i];
                }

                minCost = Math.min(minCost, cost);
            }
        }

        System.out.println(minCost);
    }
}

P3654 First Step (ファーストステップ)

解题思路

我们将以下搜索过程分为两个部分:

  1. 检查水平位置的连续空地。
  2. 检查垂直位置的连续空地。
  • 读取输入:

    • 使用 Scanner 从标准输入中读取行数 R,列数 C 和队员数 K
    • 读取接下来的 R 行字符,构建篮球场的状态矩阵 court
  • 检查水平位置的连续空地:

    • 对于每一行,从左到右检查是否存在连续 K 个空地 (.)。
    • 使用双重循环:外层循环遍历行,内层循环遍历可能的起始列位置。
  • 检查垂直位置的连续空地:

    • 对于每一列,从上到下检查是否存在连续 K 个空地 (.)。
    • 使用双重循环:外层循环遍历列,内层循环遍历可能的起始行位置。
  • 输出结果:

    • 计算并输出所有可能的站位方式的数量。
    • 当K=1时进行特殊判断,输出结果的一半。
import java.util.Scanner;

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

        int R = input.nextInt();
        int C = input.nextInt();
        int K = input.nextInt();
        input.nextLine();

        // 创建一个char类型的二维数组,用于存储篮球场
        char[][] court = new char[R][C];
        
        // 遍历篮球场,将每个位置的字母存储到二维数组中
        for (int i = 0; i < R; i++) {
            String line = input.nextLine();
            for (int j = 0; j < C; j++) {
                court[i][j] = line.charAt(j);
            }
        }

        // 调用countPossiblePositions方法,计算可以放置K个字母的位置
        int result = countPossiblePositions(R, C, K, court);
        // 输出结果
        System.out.println(result);
    }

    public static int countPossiblePositions(int R, int C, int K, char[][] court) {
        int count = 0;

        // 遍历篮球场,计算每一行可以放置K个字母的位置
        for (int i = 0; i < R; i++) {
            for (int j = 0; j <= C - K; j++) {
                boolean canPlace = true;
                // 遍历K个字母,判断每个字母是否可以放置
                for (int l = 0; l < K; l++) {
                    if (court[i][j + l] != '.') {
                        canPlace = false;
                        break;
                    }
                }
                // 如果可以放置,计数加1
                if (canPlace) {
                    count++;
                }
            }
        }

        // 遍历篮球场,计算每一列可以放置K个字母的位置
        for (int j = 0; j < C; j++) {
            for (int i = 0; i <= R - K; i++) {
                boolean canPlace = true;
                // 遍历K个字母,判断每个字母是否可以放置
                for (int l = 0; l < K; l++) {
                    if (court[i + l][j] != '.') {
                        canPlace = false;
                        break;
                    }
                }
                // 如果可以放置,计数加1
                if (canPlace) {
                    count++;
                }
            }
        }

        // 如果K为1,则结果除以2;因为此时会搜索两次
        if (K == 1) {
            return count / 2;
        }else return count;
    }
}

P1217 [USACO1.5] 回文质数 Prime Palindromes

解题思路

定义两个方法,分别判断是否时回文数以及质数,如果同时满足,则输出。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int a = input.nextInt();
        int b = input.nextInt();
        for (int i = a; i <= b; i++) {
            if (isPalindrome(i) && isPrime(i)){
                System.out.println(i);
            }
        }
    }

    public static boolean isPrime(int n){
        if (n <= 3) return n > 1;
        if (n % 6 != 1 && n % 6 != 5)return false;

        int sqrt = (int)Math.sqrt(n);
        for (int i = 5; i <= sqrt; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0){
                return false;
            }
        }
        return true;
    }

    public static boolean isPalindrome(int n){
        if (n < 0 || (n > 0 && n % 10 == 0)) return false;
        int c = 0;
        while (n > c){
            c = c * 10 + n % 10;
            n /= 10;
        }
        return c == n || n == c / 10;
    }
}

P1149 [NOIP2008 提高组] 火柴棒等式 

解题思路

  • 火柴棒数量数组: 数组 matchsticksCount 存储了数字 0 到 9 所需的火柴棒数量。
  • 读取输入: 从标准输入读取整数 n
  • 计算所有可能的等式:
    • 减去加号和等号所需的4根火柴棒(remainingSticks = n - 4)。
    • 遍历所有可能的A和B的值。
    • 计算A、B和C(C = A + B)所需的火柴棒总数。
    • 如果总数等于剩余的火柴棒数,则计数加一。
  • 计算单个数字所需的火柴棒数: 函数 matchsticksForNumber 计算一个数字的每个位数所需的火柴棒数。
import java.util.Scanner;

public class Main {
    // 火柴棒数目数组,对应数字0~9
    static int[] matchsticksCount = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
    static int n, totalCount = 0;

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

        // 计算所有可能的等式
        calculateEquations();

        System.out.println(totalCount);
    }

    private static void calculateEquations() {
        // 需要减去加号和等号所需的4根火柴棒
        int remainingSticks = n - 4;

        // 遍历所有可能的A和B值
        for (int A = 0; A <= 1111; A++) {
            for (int B = 0; B <= 1111; B++) {
                int C = A + B;

                // 计算A, B, C所需的火柴棒总数
                int totalSticks = matchsticksForNumber(A) + matchsticksForNumber(B) + matchsticksForNumber(C);

                // 如果总数匹配,则计数加一
                if (totalSticks == remainingSticks) {
                    totalCount++;
                }
            }
        }
    }

    // 计算一个数字所需的火柴棒数
    private static int matchsticksForNumber(int num) {
        //如果给定的数字为0,则返回火柴棍数量数组中的第一个元素
        if (num == 0) return matchsticksCount[0];
        //初始化火柴棍数量为0
        int sticks = 0;
        //循环计算给定数字每一位需要的火柴棍数量
        while (num > 0) {
            //将每一位需要的火柴棍数量加到总数量中
            sticks += matchsticksCount[num % 10];
            //将给定数字除以10,获取下一位数字
            num /= 10;
        }
        //返回计算得到的火柴棍数量
        return sticks;
    }
}

 P3799 小 Y 拼木棒

解题思路

  • 定义常量:

    • MOD 是常量 10^9 + 7,用于取模。
    • MAXN 是数组的最大长度 1000000 + 10
  • 读取输入:

    • 使用 Scanner 读取输入的木棒数 n 和每根木棒的长度。
    • 维护两个数组 anum,分别存储木棒的长度和每个长度的木棒数量。
    • 计算 maxa,即木棒长度的最大值。
  • 枚举两根相等的边:

    • 遍历所有可能的长度,计算能够组成正三角形的组合数。
    • 对于每个长度的木棒,若其数量 >= 2,计算组合数 C(num[i], 2)
    • 枚举被合成的边(到 i / 2 即可),根据不同的情况(木棒长度相等或不等)计算组合数,并累加到 ans 中。
  • 输出结果:

    • 打印结果,结果对 10^9 + 7 取模。

 洛谷题解中有关于排列组合在本题的应用解释。

import java.util.Scanner;

public class Main {
    private static final long MOD = 1000000007;
    private static final int MAXN = 1000000 + 10;

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

        // 读取木棒的数量
        long n = input.nextLong();
        long[] a = new long[MAXN]; // 存储木棒长度的数组
        long[] num = new long[MAXN]; // 记录每种长度的木棒数量
        long maxa = 0; // 木棒长度的最大值
        long ans = 0; // 结果变量,存储总的正三角形数目

        // 读取每根木棒的长度并更新计数数组
        for (int i = 1; i <= n; ++i) {
            a[i] = input.nextLong();
            maxa = Math.max(a[i], maxa); // 更新木棒长度的最大值
            num[(int) a[i]]++; // 更新每种长度的木棒数量
        }

        // 枚举所有可能的木棒长度
        for (int i = 2; i <= maxa; ++i) {
            // 如果有两根或以上相同长度的木棒
            if (num[i] >= 2) {
                // 计算从num[i]根木棒中选出两根的组合数
                long times = C(num[i], 2) % MOD;
                // 枚举被合成的边,长度为j的木棒
                for (int j = 1; j <= i / 2; ++j) {
                    // 用来合成的木棒长度不等
                    if (j != i - j && num[j] >= 1 && num[i - j] >= 1) {
                        ans = (ans + times * C(num[j], 1) * C(num[i - j], 1) % MOD) % MOD;
                    }
                    // 用来合成的木棒长度相等
                    if (j == i - j && num[j] >= 2) {
                        ans = (ans + times * C(num[j], 2) % MOD) % MOD;
                    }
                }
            }
        }

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

    // 计算组合数C(x, k)
    private static long C(long x, long k) {
        if (k == 1) return x;
        if (k == 2) return x * (x - 1) / 2;
        return 0;
    }
}

 P2392 kkksc03考前临时抱佛脚

解题思路

求最短时间,左右脑可以同时复习,一开始可以考虑使用贪心算法,但是使用贪心算法并不能获得最优解。于是考虑0/1背包的思想,动态规划求解。

  • 输入读取:

    • 读取每科题目的数量,并存入数组 a 中。
    • 定义 homework 数组用于存储每科题目的时间,dp 数组用于动态规划。
  • 处理每个科目:

    • 计算当前科目题目的总时间 sum
    • 使用0/1背包算法找出不超过 sum / 2 的最大时间。
    • dp 数组的含义是:对于不超过 k 的重量,能够取得的最大价值。
    • 使用动态规划更新 dp 数组:从题目时间数组 homework 中选取一个题目,尝试更新 dp 数组中的值。
    • sum - dp[sum / 2] 计算另一部分时间,并累加到 totalMinTime 中。
  • 重置动态规划数组:

    • 在处理完每个科目后,重置 dp 数组,以便处理下一个科目。
  • 输出结果:

    • 最终输出 totalMinTime,即完成所有复习所需的最短时间。
import java.util.Scanner;

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

        // 读取每个科目题目的数量
        int[] a = new int[5];
        for (int i = 1; i <= 4; i++) {
            a[i] = input.nextInt();
        }

        // 定义用于存储题目时间的数组和动态规划数组
        int[] homework = new int[21];
        int[] dp = new int[2501];

        int totalMinTime = 0;

        // 对每个科目进行处理
        for (int i = 1; i <= 4; i++) {
            int sum = 0;

            // 读取每个科目的题目时间
            for (int j = 1; j <= a[i]; j++) {
                homework[j] = input.nextInt();
                sum += homework[j];
            }

            // 0/1背包算法,计算达到 sum/2 最大时间
            for (int j = 1; j <= a[i]; j++) {
                for (int k = sum / 2; k >= homework[j]; k--) {
                    dp[k] = Math.max(dp[k], dp[k - homework[j]] + homework[j]);
                }
            }

            // 累加每个科目达到的最小复习时间
            totalMinTime += sum - dp[sum / 2];

            // 重置动态规划数组
            for (int j = 1; j <= sum / 2; j++) {
                dp[j] = 0;
            }
        }

        // 输出总最小复习时间
        System.out.println(totalMinTime);
        
    }
}

P2036 [COCI2008-2009 #2] PERKET

解题思路

  • 读取输入数据:

    • 使用 Scanner 读取食材种类数 n
    • 创建两个数组 pq 分别存储每种食材的酸度和苦度。
    • 通过循环读取每种食材的酸度和苦度,并存入相应的数组中。
  • 初始化最小绝对差:

    • 初始化 minDifferenceInteger.MAX_VALUE,用于存储当前找到的最小绝对差。
  • 枚举所有可能的配料组合:

    • 使用 for 循环枚举所有可能的组合(子集)。从 1 开始,保证至少选择一种配料。
    • 1 << n 表示 2^n,即所有可能的组合数。
  • 计算当前组合的酸度和苦度:

    • 初始化 totalP1,用于计算总酸度(乘积)。
    • 初始化 totalQ0,用于计算总苦度(加和)。
    • 使用位运算检查每种食材是否在当前组合中。
    • 如果第 j 种食材在当前组合中,更新 totalPtotalQ
  • 更新最小绝对差:

    • 计算当前组合的酸度和苦度的绝对差 currentDifference
    • 如果 currentDifference 小于 minDifference,则更新 minDifference
  • 输出结果:

    • 打印最小绝对差 minDifference
import java.util.Scanner;

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

        int n = input.nextInt();
        int[] p = new int[n];
        int[] q = new int[n];

        for (int i = 0; i < n; i++) {
            p[i] = input.nextInt();
            q[i] = input.nextInt();
        }

        // 初始化最小绝对差为最大值
        int minDifference = Integer.MAX_VALUE;

        // 枚举所有可能的配料组合(子集)
        // 从1开始,保证至少选择一种配对
        for (int i = 1; i < (1 << n); i++) {
            int totalP = 1;     // 累乘器
            int totalQ = 0;     // 累加器

            // 遍历所有配料
            for (int j = 0; j < n; j++) {
                /*
                    检查第j种配料是否在当前组合中
                    i & (1 << j)检查i的二进制表示的第j位是否为1,
                    如果为1,结果非零,说明在当前组合中,进行累加和累乘,否则跳过
                 */
                if ((i & (1 << j)) != 0) {
                    totalP *= p[j];
                    totalQ += q[j];
                }
            }

            int currentDifference = Math.abs(totalP - totalQ);
            // 更新最小值
            if (currentDifference < minDifference) {
                minDifference = currentDifference;
            }
        }

        System.out.println(minDifference);
        input.close();
    }
}
  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeShen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值