本次蓝桥杯共8题,2道选择题,6道编程题,先写一下我的感受:个人感觉较为简单(最后两题除外),如果想看一下有挑战的题,可以直接看最后两题,有详细解析。前面基本思路较顺,所以下面代码主要是我自己当时的,由ai注释,欢迎讨论。 如果您觉得有帮助,可以点赞收藏关注(狗头保命)后加的话:笔者已查到是省一了,可以关注一下,以后会发国赛的备考题目
第十六届蓝桥杯大赛软件赛省赛
Java 大学 A 组
【选手须知】
考试开始后,选手首先下载题目,并使用考场现场公布的解压密码解压试
题。
考试时间为 4 小时。考试期间选手可浏览自己已经提交的答案,被浏览的
答案允许拷贝。时间截止后,将无法继续提交或浏览答案。
对同一题目,选手可多次提交答案,以最后一次提交的答案为准。
选手必须通过浏览器方式提交自己的答案。选手在其它位置的作答或其它
方式提交的答案无效。
试题包含“结果填空”和“程序设计”两种题型。
结果填空题:要求选手根据题目描述直接填写结果。求解方式不限。不要
求源代码。把结果填空的答案直接通过网页提交即可,不要书写多余的内容。
程序设计题:要求选手设计的程序对于给定的输入能给出正确的输出结果。
考生的程序只有能运行出正确结果才有机会得分。
注意:在评卷时使用的输入数据与试卷中给出的示例数据可能是不同的。
选手的程序必须是通用的,不能只对试卷中给定的数据有效。
所有源码必须在同一文件中。调试通过后,拷贝提交。
注意:不要使用 package 语句。
注意:选手代码的主类名必须为:Main,否则会被判为无效代码。
注意:如果程序中引用了类库,在提交时必须将 import 语句与程序的其
他部分同时提交。只允许使用 Java 自带的类库。
试题 A: 数位倍数
本题总分:5 分
【问题描述】
请问在 1 至 202504 (含)中,有多少个数的各个数位之和是 5 的整数倍。
例如:5 、19 、8025 都是这样的数。
【答案提交】
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个
整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
分析:简单遍历
import java.util.*; public class Main { // 计算整数a的各位数字之和 public static int solve(int a) { int sum=0; while(a!=0) { sum+=a%10; // 累加当前个位数字 a/=10; // 移除个位数字 } return sum; } public static void main(String arg[]) { int sum=0; // 用于统计符合条件的数的个数 // 遍历从1到202504的所有整数 for(int i=1;i<=202504 ;i++) { // 调用solve方法计算各位数字之和,并判断是否能被5整除 if(solve(i)%5==0) { sum++; // 符合条件则计数器加1 } } System.out.print(sum); // 输出结果 } }
答案是 40500
试题 B: 2025
本题总分:5 分
【问题描述】
求 1 ∼ 20250412 中,有多少个数可以通过改变其数字顺序后含有 2025 。
例如,5220 、21520 可以,而 205 、225 、2200 、222555111 则不行。
提示:要求的数就是含有至少 1 个 0 、2 个 2 、1 个 5 的数。
【答案提交】
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个
整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
import java.util.*; public class Main { // 统计整数a中每个数字(0-9)出现的次数 public static int[] solve(int a) { int arr[] = new int[10]; // 创建长度为10的数组,索引0-9对应数字0-9的出现次数 while(a != 0) { int k = a % 10; // 获取当前个位数字 arr[k]++; // 对应数字的计数器加1 a /= 10; // 移除个位数字 } return arr; // 返回统计数组 } public static void main(String arg[]) { int sum = 0; // 用于统计符合条件的数的个数 // 遍历从2025到20250412的所有整数 for(int i = 2025; i <= 20250412; i++) { int arr[] = solve(i); // 调用solve方法统计每个数字的出现次数 // 判断是否满足条件:数字2至少出现2次、数字0至少出现1次、数字5至少出现1次 if(arr[2] >= 2 && arr[0] >= 1 && arr[5] >= 1) { sum++; // 符合条件则计数器加1 } } System.out.print(sum); // 输出结果 } }
答案:506754
试题 C: 变换数组
时间限制: 3.0s
内存限制: 512.0MB
本题总分:10 分
【问题描述】
输入一个数组 a ,包含有 n 个元素 a1, a2, · · · , an 。对这个数组进行 m 次
变换,每次变换会将数组 a 中的每个元素 ai 转换为 ai · bitCount(ai) 。其中
bitCount(x) 表示数字 x 的二进制表示中 1 出现的次数,例如 bitCount(3) = 2 ,
因为 3 的二进制表示为 11 ,其中 1 出现了两次。
请输出变换之后的数组内容。
【输入格式】
输入的第一行包含一个正整数 n ,表示数组 a 中的元素个数。
第二行包含 n 个整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。
第三行包含一个整数 m ,表示变换次数。
【输出格式】
输出一行,包含 n 个整数,相邻整数之间使用一个空格分隔,表示变换之
后得到的数组 a 。
【样例输入】
2
5 7
2
【样例输出】
20 63
【样例说明】
5 = (101)2 ,7 = (111)2 ,第一次变化后 a = [10, 21] 。
10 = (1010)2 ,21 = (10101)2 ,第二次变换后 a = [20, 63] 。
【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ n ≤ 10 ;
对于 60% 的评测用例,1 ≤ n ≤ 100 ;
对于所有评测用例,1 ≤ n ≤ 103 ,0 ≤ m ≤ 5 ,0 ≤ ai ≤ 1000 。
package t3; import java.util.*; public class Main { // 计算整数a的二进制表示中1的个数 public static int solve(int a) { int sum = 0; while(a != 0) { if(a % 2 == 1) { // 判断当前位是否为1 sum++; } a /= 2; // 右移一位,相当于除以2 } return sum; } // 对数组中的每个元素执行变换:将元素替换为其"二进制中1的个数 × 自身的值" public static int[] solve2(int arr[]) { int arr1[] = new int[arr.length]; for(int i = 0; i < arr.length; i++) { arr1[i] = solve(arr[i]) * arr[i]; // 计算变换后的值 } return arr1; // 返回变换后的数组 } public static void main(String arg[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 输入数组长度 // 输入原始数组 int arr[] = new int[n]; for(int i = 0; i < n; i++) { arr[i] = sc.nextInt(); } int m = sc.nextInt(); // 输入变换次数 // 执行m次变换 for(int i = 0; i < m; i++) { int arr1[] = solve2(arr); // 执行变换 for(int j = 0; j < n; j++) { arr[j] = arr1[j]; // 更新原数组为变换后的结果 } } // 输出最终结果 for(int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } } }
试题 D: 最短距离
时间限制: 3.0s
内存限制: 512.0MB
本题总分:10 分
【问题描述】
在一条一维的直线上,存在着 n 台显示器和 n 个电源插座。老师给小蓝布
置了个任务:负责将每台显示器通过电源线与一个插座相连接(每个插座最多
只能给一台显示器供电);同时,老师希望所消耗的电源线的长度尽可能的少,
请你帮小蓝计算下电源线的最小消耗长度为多少?
为了便于计算,你只需要考虑直线距离即可。
【输入格式】
输入的第一行包含一个正整数 n 。
接下来 n 行,每行包含一个整数 xi ,依次表示每台显示器的坐标。
接下来 n 行,每行包含一个整数 yi ,依次表示每个插座的坐标。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
2
0
1
2
3
【样例输出】
4
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 10 ,0 ≤ xi, yi ≤ 100 ;
对于 40% 的评测用例,1 ≤ n ≤ 100 ,0 ≤ xi, yi ≤ 10e3 ;
对于 60% 的评测用例,1 ≤ n ≤ 1000 ,0 ≤ xi, yi ≤ 10e5 ;
对于 80% 的评测用例,1 ≤ n ≤ 10000 ,0 ≤ xi, yi ≤ 10e9 ;
对于所有评测用例,1 ≤ n ≤ 50000 ,0 ≤ xi, yi ≤ 10e9 。
import java.util.*; import java.math.*; public class Main { public static void main(String arg[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 输入数组长度 // 定义两个长整型数组 long sum = 0; // 用于存储最终结果 long arr[] = new long[n]; // 第一个数组 long arr1[] = new long[n]; // 第二个数组 // 输入第一个数组的元素 for(int i = 0; i < n; i++) { arr[i] = sc.nextLong(); } // 输入第二个数组的元素 for(int i = 0; i < n; i++) { arr1[i] = sc.nextLong(); } // 分别对两个数组进行排序 Arrays.sort(arr1); Arrays.sort(arr); // 计算排序后对应位置元素差值的绝对值之和 for(int i = 0; i < n; i++) { sum += Math.abs(arr[i] - arr1[i]); // 累加每对元素的差值绝对值 } // 输出结果 System.out.print(sum); } }
试题 E: 冷热数据队列
时间限制: 3.0s
内存限制: 512.0MB
本题总分:15 分
【问题描述】
小蓝是一名计算机专业的学生,最近他学习了《操作系统》、《数据结构》
等课程,他设计了一种名为 “冷热数据队列” 的数据结构,来对数据页进行管
理。
冷热数据队列 q 可以看做由两个子队列组成:长度为 n1 的热数据队列 q1
和长度为 n2 的冷数据队列 q2 。当我们需要访问某个数据页 p 时:
1)若 p 不在队列 q 中(即既不在 q1 中,也不在 q2 中),则加载数据页 p
,并插入到 q2 的首部。
2)若 p 已经在队列 q 中,则将 p 移动至 q1 首部。
3)当 q1 或 q2 队列容量不足时,会将其尾部的数据页淘汰出去。
4)当 q1 已满,但 q2 未满时,从 q1 中淘汰出的数据页会移动到 q2 首部。
【输入格式】
输入的第一行包含两个正整数 n1, n2 ,用一个空格分隔。
第二行包含一个整数 m ,表示操作次数。
第三行包含 m 个正整数 v1, v2, · · · , vm ,表示依次访问到的数据页的编号,
相邻整数之间使用一个空格分隔。
【输出格式】
输出两行。
第一行包含若干个整数,相邻整数之间使用一个空格分隔,依次表示 q1 中
的数据页。
第二行包含若干个整数,相邻整数之间使用一个空格分隔,依次表示 q2 中
的数据页。
【样例输入】
3 3
10
1 2 3 4 3 2 2 1 3 4
【样例输出】
4 3 2
1
【样例说明】

【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n1, n2 ≤ 10 ,1 ≤ m ≤ 10 ;
对于 40% 的评测用例,1 ≤ n1, n2 ≤ 20 ,1 ≤ m ≤ 100 ;
对于 60% 的评测用例,1 ≤ n1, n2 ≤ 100 ,1 ≤ m ≤ 1000 ;
对于 80% 的评测用例,1 ≤ n1, n2 ≤ 103 , 1 ≤ m ≤ 10e4 ;
对于所有评测用例,1 ≤ n1, n2 ≤ 104 ,1 ≤ m ≤ 10e5 ,0 ≤ vi ≤ 10e4 。
package t5; import java.util.*; public class Main { // 定义两个队列:q1和q2 static Queue<Integer> q1 = new LinkedList<>(); static Queue<Integer> q2 = new LinkedList<>(); // 记录两个队列的当前大小 static int size1 = 0, size2 = 0; // 从队列中移除所有值为a的元素 public static Queue<Integer> remove(int a, Queue<Integer> q1) { Queue<Integer> q = new LinkedList<>(); while (!q1.isEmpty()) { int t = q1.poll(); if (t != a) { q.add(t); // 将不等于a的元素添加到新队列 } } return q; } // 向队列添加元素a,并根据k值和队列容量调整队列 public static Queue<Integer> add(int a, int m1, int m2, Queue<Integer> q1, int k) { Queue<Integer> q = new LinkedList<>(); q.add(a); // 先添加元素a到队首 while (!q1.isEmpty()) { int t = q1.poll(); q.add(t); // 将原队列元素依次添加到新队列 if (k == 1) { // 处理q1队列 m1--; if (m1 == 1) { // 当q1剩余容量为1时 if (k == 1 && size2 < m2) { // 如果q2未满 int t1 = q1.poll(); // 从原队列取出下一个元素 q2.add(t1); // 将该元素添加到q2 } break; // 停止处理原队列 } } else { // 处理q2队列 m2--; if (m2 == 1) { // 当q2剩余容量为1时 break; // 停止处理原队列 } } } return q; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int m1 = sc.nextInt(); // q1的最大容量 int m2 = sc.nextInt(); // q2的最大容量 int n = sc.nextInt(); // 操作次数 for (int i = 0; i < n; i++) { int k = sc.nextInt(); // 输入元素 if (!q1.contains(k) && !q2.contains(k)) { // 元素不在任何队列中 q2 = add(k, m1, m2, q2, 2); // 添加到q2 if (size2 < n) { size2++; // 更新q2大小 } } else { // 元素已存在 if (q2.contains(k)) { // 元素在q2中 q2 = remove(k, q2); // 从q2移除 q1 = add(k, m1, m2, q1, 1); // 添加到q1 size2--; // 更新q2大小 if (size1 < m1) { size1++; // 更新q1大小 } } else { // 元素在q1中 q1 = remove(k, q1); // 从q1移除 q1 = add(k, m1, m2, q1, 1); // 重新添加到q1(移至队首) } } } // 输出q1的所有元素 while (!q1.isEmpty()) { int t = q1.poll(); System.out.print(t + " "); } System.out.println(); // 输出q2的所有元素 while (!q2.isEmpty()) { int t = q2.poll(); System.out.print(t + " "); } } }
试题 F: 拼好数
时间限制: 3.0s
内存限制: 512.0MB
本题总分:15 分
【问题描述】
我们将含有不少于 6 个 6 的数视为一个好数。例如 666666, 162636465666
是好数,12366666 不是好数。
给定 n 个正整数 ai,你可以把这些数分成若干组拼起来,每组内的数可以
按任意顺序拼,但一组最多只能有 3 个数。求最多可以得到多少个好数。
【输入格式】
输入的第一行包含一个正整数 n 。
第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。
【输出格式】
输出一行包含一个整数表示答案,即最多可以得到的好数的数量
【样例输入 1】
3
66 66 66
【样例输出 1】
1
【样例输入 2】
7
666666 16166 6696 666 6 6 6
【样例输出 2】
2
【评测用例规模与约定】
对于 70% 的评测用例,1 ≤ n ≤ 20 ;
对于所有评测用例,1 ≤ n ≤ 1000 ,0 ≤ ai ≤ 10e9 。
package t6; import java.util.*; public class Main { // 计算整数a中数字6的出现次数 public static int c(int a) { int size = 0; while(a != 0) { int k = a % 10; // 获取当前个位数字 a /= 10; // 移除个位数字 if(k == 6) { // 如果当前数字是6 size++; // 计数器加1 } } return size; } public static void main(String arg[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 输入整数的数量 int sum = 0; // 用于统计满足条件的组合数 // arr[i]表示数字6出现次数为i的整数的数量,i的范围是0到5 int arr[] = new int[6]; // 读取输入的n个整数,并统计数字6的出现次数 for(int i = 0; i < n; i++) { int k = sc.nextInt(); int t = c(k); // 计算数字6的出现次数 if(t >= 6) { // 如果出现次数大于等于6,直接计入结果 sum++; } else { // 否则,将其计入对应的桶中 arr[t]++; } } // 处理出现次数3到5的情况,尝试两两组合使其和为6 for(int i = 5; i >= 3; i--) { int t = 6 - i; // 寻找能与i组合成6的数 while(arr[i] != 0) { if(i == t) { // 如果i和t相等,无法组合,退出循环 break; } // 计算可以组合的最大对数 int k = Math.min(arr[i], arr[t]); arr[i] -= k; // 减少对应桶的数量 arr[t] -= k; sum += k; // 累加组合数 t++; // 尝试更大的t值 } // 如果还有剩余的i,且无法再组合,处理剩余的i if(arr[i] != 0) { sum += arr[i] / 2; // 每两个i可以组成一个满足条件的组合 arr[i] = 1; // 剩余1个无法组合 break; } } // 特殊情况处理:一个出现3次6的和两个出现2次6的可以组成一个满足条件的组合 if(arr[3] == 1 && arr[2] >= 2) { arr[2] -= 2; sum += 1; } // 处理出现次数为2的情况,每三个出现2次6的可以组成一个满足条件的组合 if(arr[2] >= 3) { int k = arr[2] / 3; sum += k; } // 输出最终结果 System.out.print(sum); } }
试题 G: 甘蔗
时间限制: 3.0s
内存限制: 512.0MB
本题总分:20 分
【问题描述】
小蓝种了一排甘蔗,甘蔗共 n 根,第 i 根甘蔗的高度为 ai 。小蓝想砍一些
甘蔗下来品尝,但是他有强迫症,不希望甘蔗的高度显得乱糟糟的。具体来说,
他给出了一个大小为 m 的整数集合 B = {b1, b2, · · · , bm} ,他希望在砍完甘蔗后,
任意两根相邻的甘蔗之间的高度差 |ai − ai+1| 都要在这个集合 B 中。小蓝想知道
他最少需要砍多少根甘蔗(对于高度为 h 的甘蔗,他可以将其砍成 x 高度的甘
蔗,x ∈ {0, 1, 2, · · · , h − 1} )。
【输入格式】
输入的第一行包含两个正整数 n, m ,用一个空格分隔。
第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。
第三行包含 m 个正整数 b1, b2, · · · , bm ,相邻整数之间使用一个空格分隔。
【输出格式】
输出一行包含一个整数表示答案。如果不能满足条件,输出 −1 。
【样例输入 1】
6 3
6 7 3 4 9 12
2 3 5
【样例输出 1】
2
【样例说明 1】
其中一种方案:将 a2 砍为 3 ,再将 a3 砍为 1 。
【样例输入 2】
2 1
4 5
6
【样例输出 2】
-1
【评测用例规模与约定】
对于 40% 的评测用例,1 ≤ n, m ≤ 8 ;
对于所有评测用例,1 ≤ n, m ≤ 500 ,1 ≤ ai ≤ 1000 ,0 ≤ bi ≤ 1000 。
package t7; import java.util.*; public class Main { public static void main(String arg[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 输入数组的长度 int m = sc.nextInt(); // 查询的次数 // 读取数组元素 int arr[] = new int[n]; for(int i = 0; i < n; i++) { arr[i] = sc.nextInt(); } // 读取m次查询,但未对查询进行任何处理 for(int i = 0; i < m; i++) { int k = sc.nextInt(); // 读取查询值,但未使用 } // 直接输出-1,未对查询进行任何处理 System.out.print(-1); } }
这题是没思路,dp还不熟练 ,此题要用动态规划
算法思路:
-
动态规划状态定义:
dp[i][h]
表示将第i
根甘蔗切割为高度h
时的最小切割次数。
-
状态转移:
- 对于每根甘蔗
i
,遍历其所有可能的切割高度h_i
。 - 对于前一根甘蔗的每个可能高度
h_prev
,如果高度差|h_i - h_prev|
属于允许集合B
,则更新dp[i][h_i]
。
- 对于每根甘蔗
-
初始状态:
- 第一根甘蔗的切割次数为 0(不切割)或 1(切割)。
-
结果计算:
- 遍历最后一根甘蔗的所有可能高度,取最小切割次数。如果所有状态都不可达,则返回
-1。
- 遍历最后一根甘蔗的所有可能高度,取最小切割次数。如果所有状态都不可达,则返回
代码:
import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 甘蔗数量 int m = sc.nextInt(); // 允许的高度差数量 int[] a = new int[n]; // 每根甘蔗的原始高度 for (int i = 0; i < n; i++) a[i] = sc.nextInt(); // 存储允许的高度差集合 Set<Integer> B = new HashSet<>(); for (int i = 0; i < m; i++) B.add(sc.nextInt()); int maxH = 1000; // 最大可能的高度 // dp[i][h] 表示第i根甘蔗切割为高度h时的最小切割次数 int[][] dp = new int[n][maxH + 1]; for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE); // 初始化第一根甘蔗的dp值 for (int h = 0; h <= a[0]; h++) { // 如果不切割,代价为0;否则代价为1 dp[0][h] = (h == a[0]) ? 0 : 1; } // 动态规划填充dp数组 for (int i = 1; i < n; i++) { // 遍历每根甘蔗 for (int h_i = 0; h_i <= a[i]; h_i++) { // 当前甘蔗的可能高度 for (int h_prev = 0; h_prev <= a[i-1]; h_prev++) { // 前一根甘蔗的可能高度 // 如果前一个状态不可达,跳过 if (dp[i-1][h_prev] == Integer.MAX_VALUE) continue; // 计算高度差 int diff = Math.abs(h_i - h_prev); // 如果高度差在允许集合中 if (B.contains(diff)) { // 计算当前状态的代价:前一根甘蔗的代价 + 当前是否切割 int cost = dp[i-1][h_prev] + ((h_i == a[i]) ? 0 : 1); // 更新最小代价 dp[i][h_i] = Math.min(dp[i][h_i], cost); } } } } // 查找最后一根甘蔗的所有可能高度中的最小代价 int res = Integer.MAX_VALUE; for (int h = 0; h <= a[n-1]; h++) { res = Math.min(res, dp[n-1][h]); } // 输出结果,如果无法满足条件则返回-1 System.out.println(res == Integer.MAX_VALUE ? -1 : res); } }
试题 H: 原料采购
时间限制: 3.0s
内存限制: 512.0MB
本题总分:20 分
【问题描述】
小蓝负责一家工厂的原料采购。
工厂有一辆运货卡车,其容量为 m 。
工厂附近的采购点都在同一条路的同一方向上,一共有 n 个,每个采购点
和工厂的距离各不相同。其中,第 i 个采购点的价格为 ai ,库存为 bi ,距离为
ci 。
卡车每行驶一单位长度的路径就需要额外花费 o 。(返程没有花费,你也可
以认为 o 实际是行驶两单位长度的花费)
请计算将卡车装满最少需要花费多少钱,如果没有任何方案可以装满请输
出 −1 。
【输入格式】
输入的第一行包含三个正整数 n, m, o ,相邻整数之间使用一个空格分隔。
接下来 n 行,每行包含三个正整数 ai, bi, ci 表示一个采购点,相邻整数之间
使用一个空格分隔。
【输出格式】
输出一行包含一个整数表示答案,即装满卡车所需的最小花费。
【样例输入】
3 5 1
99 9 1
3 4 99
1 2 190
【样例输出】
201
【评测用例规模与约定】
对于 40% 的评测用例,n ≤ 5000 ,m ≤ 50000 ;
对于 60% 的评测用例,m ≤ 105 ;
对于所有评测用例,1 ≤ n ≤ 105 ,1 ≤ m, o ≤ 109 ,1 ≤ ai, bi, ci ≤ 109 ,保
证对于 i > 1 ,一定有 ci−1 < ci 。
package t8; import java.util.*; public class Main { public static void main(String arg[]) { // 创建Scanner对象用于读取输入 Scanner sc = new Scanner(System.in); // 读取采购点的数量n int n = sc.nextInt(); // 读取卡车的容量m int m = sc.nextInt(); // 读取每行驶一单位长度的额外花费o int o = sc.nextInt(); // 创建一个room类型的数组arr,用于存储n个采购点的信息 room arr[] = new room[n]; // 创建一个数组sum,用于存储前i个采购点的库存总和,长度为n + 1,sum[0]初始化为0 int sum[] = new int[n + 1]; sum[0] = 0; // 遍历每个采购点,读取并存储其价格a、库存b、距离c for (int i = 0; i < n; i++) { int a = sc.nextInt(); int b = sc.nextInt(); int c = sc.nextInt(); // 创建room对象并存储到arr数组中 arr[i] = new room(a, b, c); // 计算并更新前i + 1个采购点的库存总和 sum[i + 1] = b + sum[i]; } // 如果所有采购点的库存总和小于卡车容量,无法装满,输出-1 if (sum[n] < m) { System.out.print(-1); } else { // 初始化最小花费为一个较大的值 int min = 100000000; // 遍历每个采购点 for (int i = 0; i < n; i++) { // 如果所有采购点的库存总和小于卡车容量,跳过本次循环 if (sum[n] < m) { continue; } // 创建一个新的room数组arr1,大小为i + 1,用于存储前i + 1个采购点的信息 room arr1[] = new room[i + 1]; // 将前i + 1个采购点的信息从arr数组复制到arr1数组 for (int j = 0; j <= i; j++) { arr1[j] = arr[j]; } // 对arr1数组按采购点的价格a进行升序排序 Arrays.sort(arr1, (p1, p2) -> p1.a - p2.a); // 初始化当前花费为0 int cost = 0; // 剩余需要装载的货物量初始化为卡车容量m int k = m; // 遍历排序后的arr1数组(前i + 1个采购点) for (int j = 0; j <= i; j++) { // 如果剩余需要装载的货物量大于等于当前采购点的库存 if (k >= arr1[j].b) { // 从剩余量中减去当前采购点的库存 k -= arr1[j].b; // 增加当前采购点的采购花费 cost += arr1[j].b * arr1[j].a; } else { // 剩余量小于当前采购点的库存,只采购剩余量 cost += k * arr1[j].a; // 已经装满,跳出循环 break; } } // 加上第i个采购点的距离相关花费(距离乘以每单位长度的花费o) cost += arr[i].c * o; // 如果当前花费小于最小花费,更新最小花费 if (cost < min) { min = cost; } } // 输出最小花费 System.out.print(min); } } } // 定义room类,用于存储采购点的信息 class room { // 采购点的价格 int a; // 采购点的库存 int b; // 采购点的距离 int c; // 构造函数,用于初始化room对象的属性 public room(int a, int b, int c) { this.a = a; this.b = b; this.c = c; } }
很明显这只是暴力求解,正确解法如下:
算法思路
-
问题分析:
- 卡车必须装满
m
单位的原料。 - 每次只能选择一个最远的采购点(因为采购后需直接返回工厂)。
- 总花费 = 原料成本 + 运输成本(仅单程,即到达最远采购点的距离乘以
o
)。
- 卡车必须装满
-
关键观察:
- 选择最远的采购点
i
后,可以采购其左侧所有采购点的原料(因为距离严格递增)。 - 为了最小化原料成本,应优先选择价格低的采购点。
- 选择最远的采购点
-
贪心策略:
- 枚举每个可能的最远采购点
i
。 - 对所有
j ≤ i
的采购点按价格排序,优先采购低价原料。 - 计算装满卡车的最小原料成本,并加上运输到
i
的成本。
- 枚举每个可能的最远采购点
优化算法实现
import java.util.*; public class Main { static class Point { int a, b, c; Point(int a, int b, int c) { this.a = a; this.b = b; this.c = c; } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); long m = sc.nextLong(); // 使用long避免溢出 int o = sc.nextInt(); Point[] points = new Point[n]; long totalSupply = 0; for (int i = 0; i < n; i++) { int a = sc.nextInt(); int b = sc.nextInt(); int c = sc.nextInt(); points[i] = new Point(a, b, c); totalSupply += b; } // 检查总库存是否足够 if (totalSupply < m) { System.out.println(-1); return; } long minCost = Long.MAX_VALUE; // 枚举最远的采购点i for (int i = 0; i < n; i++) { // 复制前i+1个采购点并按价格排序 Point[] candidates = Arrays.copyOfRange(points, 0, i + 1); Arrays.sort(candidates, (p1, p2) -> p1.a - p2.a); long remaining = m; long currentCost = 0; // 贪心选择:优先采购低价原料 for (Point p : candidates) { if (remaining <= 0) break; long buy = Math.min(p.b, remaining); currentCost += buy * p.a; remaining -= buy; } // 加上运输成本 currentCost += (long) points[i].c * o; minCost = Math.min(minCost, currentCost); } System.out.println(minCost); } }