第十六届蓝桥杯Java大学A组真题解析

本次蓝桥杯共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还不熟练 ,此题要用动态规划

算法思路:

  1. 动态规划状态定义

    • dp[i][h] 表示将第 i 根甘蔗切割为高度 h 时的最小切割次数。
  2. 状态转移

    • 对于每根甘蔗 i,遍历其所有可能的切割高度 h_i
    • 对于前一根甘蔗的每个可能高度 h_prev,如果高度差 |h_i - h_prev| 属于允许集合 B,则更新 dp[i][h_i]
  3. 初始状态

    • 第一根甘蔗的切割次数为 0(不切割)或 1(切割)。
  4. 结果计算

    • 遍历最后一根甘蔗的所有可能高度,取最小切割次数。如果所有状态都不可达,则返回 -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;
    }
}

很明显这只是暴力求解,正确解法如下:

算法思路

  1. 问题分析

    • 卡车必须装满 m 单位的原料。
    • 每次只能选择一个最远的采购点(因为采购后需直接返回工厂)。
    • 总花费 = 原料成本 + 运输成本(仅单程,即到达最远采购点的距离乘以 o)。
  2. 关键观察

    • 选择最远的采购点 i 后,可以采购其左侧所有采购点的原料(因为距离严格递增)。
    • 为了最小化原料成本,应优先选择价格低的采购点。
  3. 贪心策略

    • 枚举每个可能的最远采购点 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);
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值