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到9的所有排列组合生成可能的三位数组合。
检查比例条件:对于每一组三位数,检查它们是否满足比例A:B:C。
输出结果:若满足条件的组合存在,则输出这些组合,否则输出 "No!!!"。
具体步骤如下:
生成所有可能的三位数组合:
permute
方法生成所有1到9的排列,并在生成排列的过程中,检查每一个排列是否满足条件。检查比例条件:
- 从排列中提取出三个三位数x, y, z。
- 检查
y * A == x * B
和z * B == y * C
。输出结果:
- 满足条件的组合存入
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个整数相加,计算这些组合的和,并统计其中有多少种和是素数。这个问题可以分解为以下几个步骤:
- 生成所有从n个数中选择k个数的组合。
- 计算每个组合的和。
- 检查每个和是否为素数。
- 统计并输出素数的个数。
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();
list.stream()
: 将列表转换为流(Stream)。mapToInt(Integer::intValue)
: 对流中的每个元素执行一个函数,将元素转换为整数。这里使用的是Integer::intValue
方法,它将每个元素转换为其整数值。sum()
: 对流中的整数进行求和操作。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 组合的输出
解题思路
理解题目要求:
- 输入两个自然数
n
和r
,其中1 < n < 21
且0 ≤ r ≤ n
。(可以打表)- 需要从
1
到n
中选择r
个数,生成所有可能的组合。- 每个组合中的数字按升序排列。
- 输出时,每个数字占据3个字符宽度,并且所有组合按字典序排列。
生成组合:
- 组合的特点是无序的,即
{1, 2, 3}
和{3, 2, 1}
被视为同一组合。- 可以使用递归或者迭代的方法生成组合。
- 递归方法更容易理解和实现,特别是在生成固定长度的组合时。
递归方法:
- 递归生成组合时,首先选定一个数字,然后从剩余数字中继续选择。
- 终止条件是当前组合长度达到
r
。- 为了确保组合按字典序排列,每次选择数字从当前数字的下一个数字开始选择。
格式化输出:
- 输出每个数字时,需要确保其占3个字符宽度,可以使用
String.format
或System.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
个小方块组成的棋盘改造成合法图案,要求改变的格子数量最少。合法图案的定义是:
- 最上方若干行(至少一行)全是白色的。
- 接下来若干行(至少一行)全是蓝色的。
- 剩下的行(至少一行)全是红色的。
可以用动态规划的思想来求解这个问题。具体步骤如下:
定义状态:
- 用三个数组
whiteCost[i]
,blueCost[i]
和redCost[i]
分别表示将第i
行全部变成白色、蓝色或红色所需要的代价。- 使用一个三维数组
dp[i][j][k]
表示前i
行中,第j
行及之前的所有行都涂成白色,第k
行及之后的所有行都涂成红色的最小代价。初始化:
- 计算出每一行变成白色、蓝色、红色的代价。
状态转移:
- 遍历所有可能的分割点,将前面部分变为白色,中间部分变为蓝色,后面部分变为红色,并记录最小代价。
求解:
- 遍历所有可能的分割方式,计算最终的最小代价。
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 (ファーストステップ)
解题思路
我们将以下搜索过程分为两个部分:
- 检查水平位置的连续空地。
- 检查垂直位置的连续空地。
读取输入:
- 使用
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
和每根木棒的长度。- 维护两个数组
a
和num
,分别存储木棒的长度和每个长度的木棒数量。- 计算
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
。- 创建两个数组
p
和q
分别存储每种食材的酸度和苦度。- 通过循环读取每种食材的酸度和苦度,并存入相应的数组中。
初始化最小绝对差:
- 初始化
minDifference
为Integer.MAX_VALUE
,用于存储当前找到的最小绝对差。枚举所有可能的配料组合:
- 使用
for
循环枚举所有可能的组合(子集)。从1
开始,保证至少选择一种配料。1 << n
表示2^n
,即所有可能的组合数。计算当前组合的酸度和苦度:
- 初始化
totalP
为1
,用于计算总酸度(乘积)。- 初始化
totalQ
为0
,用于计算总苦度(加和)。- 使用位运算检查每种食材是否在当前组合中。
- 如果第
j
种食材在当前组合中,更新totalP
和totalQ
。更新最小绝对差:
- 计算当前组合的酸度和苦度的绝对差
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();
}
}