蓝桥杯第十四届Java大学生B组省赛真题及题解

试题 A: 阶乘求和

1711674580016

[题解]

由于从 40 ! 40! 40! 开始末尾 9 位都为 0,即我们只要关注前 39 个阶乘相加即可。


public class Main {

    static final long MOD = (int) 1e9;

    public static void main(String[] args) {
        long ans = 0;
        long num = 1;
        for (int i = 1; i < 40; i++) {
            num = (num * i) % MOD;
            ans = (ans + num) % MOD;
        }
        System.out.println(ans);
    }
}

[答案]

420940313

试题 B: 幸运数字

1711676180422

[题解]


public class Main {

    public static void main(String[] args) {
        int ans = 1;
        for (int i = 0; ; ans++) {
            String binaryString = Integer.toBinaryString(ans);
            String octalString = Integer.toOctalString(ans);
            String decString = Integer.toString(ans);
            String hexString = Integer.toHexString(ans);
            int binSum = calc(binaryString);
            int octSum = calc(octalString);
            int decSum = calc(decString);
            int hexSum = calc(hexString);
            if (ans % binSum == 0 && ans % octSum == 0 && ans % decSum == 0 && ans % hexSum == 0) i++;
            if (i == 2023) {
                System.out.println(ans);
                break;
            }
        }

    }

    private static int calc(String s) {
        int res = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
                res += s.charAt(i) - '0';
            } else if (s.charAt(i) >= 'a' && s.charAt(i) <= 'f') {
                res += s.charAt(i) - 'a' + 10;
            }
        }
        return res;
    }
}

[答案]

215040

试题 C: 数组分割

1711677838344

[题解]

要使得两个集合都为偶数且原集合所有元素都要分完,则需要若干个偶数与偶数个奇数,出现奇数个奇数,则一定无法实现题目要求。

当其中一个集合的元素确定,另一个集合也随之确定,即我们只要考虑一个集合有多少种可能性。

将两个奇数看为一个整体,我们能得到集合所有可能为 2 e v e n + o d d − 1 2^{even + odd - 1} 2even+odd1, 需要特判没有奇数的情况,防止数值错误,即正确的为 2 e v e n 2^{even} 2even


import java.util.*;

/**
 * 2023 分割数组 排列组合 集合 快速幂
 *
 */
public class Main {

    static int MOD = (int) 1e9 + 7;

    public static void main(String[] args) {
        new Main().go();
    }
    void go() {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        long ans = 0;
        for(int i = 0; i < n; i++) {
            int m = input.nextInt(), odd = 0, even = 0;
            for(int j = 0; j < m; j++) {
                if (input.nextInt() % 2 == 0) even++;
                else odd++;
            }
            if (odd % 2 == 0) ans = fastPow(2, even) * fastPow(2, odd == 0 ? 0 : odd - 1) % MOD;
            System.out.println(ans);
            ans = 0;
        }


    }
    long fastPow(long a, long b) {
        if (b == -1) return 1;
        long res = 1;
        while(b > 0) {
            if((b & 1) == 1) res = res *a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return res % MOD;
    }

}

试题 D: 矩形总面积

1711679208306

[题解]

矩形总面积 = 两个矩形面积之和 - 重叠部分矩形面积

两个矩形面积很好算,我们只要确定重叠部分矩形即可。

通过归纳可以发现重叠部分矩形左下角坐标 ( m a x ( x 1 , x 3 ) , m a x ( y 1 , y 3 ) ) (max(x_1, x_3), max(y_1,y_3)) (max(x1,x3),max(y1,y3)), 右上角坐标 ( m i n ( x 2 , x 4 ) , m i n ( y 2 , y 4 ) ) (min(x_2, x_4), min(y_2,y_4)) (min(x2,x4),min(y2,y4))

当左下角坐标在右上角坐标右边时,代表两个矩形不重叠,即重叠部分矩形面积为零。


import java.io.*;
/**
 * 2023 矩形总面积 数学
 *
 */
public class Main {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));

    public static void main(String[] args) throws Exception{
        new Main().go();
    }
    void go() throws Exception{
        String[] s = br.readLine().split(" ");
        int x1 = Integer.valueOf(s[0]);
        int y1 = Integer.valueOf(s[1]);
        int x2 = Integer.valueOf(s[2]);
        int y2 = Integer.valueOf(s[3]);
        int x3 = Integer.valueOf(s[4]);
        int y3 = Integer.valueOf(s[5]);
        int x4 = Integer.valueOf(s[6]);
        int y4 = Integer.valueOf(s[7]);
        long s1 = (long)(x2 - x1) * (long)(y2 - y1);
        long s2 = (long)(x4 - x3) * (long)(y4 - y3);
        int x_left = Math.max(x1, x3);
        int y_left = Math.max(y1, y3);
        int x_right = Math.min(x2, x4);
        int y_right = Math.min(y2, y4);
        int length = x_right - x_left;
        int width = y_right - y_left;
        long s3 = 0;
        if (length > 0 && width > 0) s3 = (long)length * (long)width;
        pw.println(s1 + s2 - s3);
        pw.flush();

    }
}

试题 E: 蜗牛

1711680520341

[题解]

明确蜗牛爬行的起点为 (0, 0), 终点为 (x, 0)。 x 表示最后一根竹竿底部位置

蜗牛每次爬行面临两个选择,①顺着地面;②走传送门

每次选择都要最优,且具备无后效性,可以使用 dp 来做。

定义状态 f(i,j), i 表示在第几根竹竿上; j = 0 表示蜗牛在竹竿底部, j = 1 表示蜗牛在传送门

初始状态 f ( 1 , 0 ) = x ​ f(1,0) = x​ f(1,0)=x; f ( 1 , 1 ) = x + y 0.7 ​ f(1,1) = x + \frac{y} {0.7}​ f(1,1)=x+0.7y。 x 表示第一根竹竿的位置,y 表示第一个传送门的位置

f ( i , 0 ) = m i n ( y 1.3 + f ( i − 1 , 1 ) , Δ x + f ( i − 1 , 0 ) ) ​ f(i,0) = min(\frac{y}{1.3} + f(i - 1, 1), \Delta x + f(i - 1, 0))​ f(i,0)=min(1.3y+f(i1,1),Δx+f(i1,0))

新传送门在传送到位置上方 f ( i , 1 ) = m i n ( f ( i − 1 , 1 ) , Δ x + Δ y 0.7 + f ( i − 1 , 0 ) ) f(i,1) = min(f(i - 1, 1), \Delta x + \frac{\Delta y}{0.7}+ f(i - 1, 0)) f(i,1)=min(f(i1,1),Δx+0.7Δy+f(i1,0))

新传送门在传送到位置下方 f ( i , 1 ) = m i n ( f ( i − 1 , 1 ) , Δ x + Δ y 1.3 + f ( i − 1 , 0 ) ) f(i,1) = min(f(i - 1, 1), \Delta x + \frac{\Delta y}{1.3}+ f(i - 1, 0)) f(i,1)=min(f(i1,1),Δx+1.3Δy+f(i1,0))

y y y 表示传送门的位置, Δ x \Delta x Δx 表示两竹竿之间的距离, Δ y \Delta y Δy 表示到新传送门距离

为节省空间,代码中的 x1 就是 f ( i , 0 ) f(i,0) f(i,0), x2 就是 f ( i , 1 ) f(i,1) f(i,1), y1 存放变化后的 x1,以防对 x2 计算产生干扰。


import java.io.*;
import java.util.*;
/**
 * 2023 蜗牛 线性dp
 *
 */
public class Main {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));

    public static void main(String[] args) throws Exception{
        new Main().go();
    }
    void go() throws Exception{
        int n = Integer.valueOf(br.readLine());
        String[] s = br.readLine().split(" ");
        int[] nums = Arrays.stream(s).mapToInt(Integer::parseInt).toArray();
        if (n == 1) {
            pw.printf("%.2f", (double)(nums[0]));
            pw.flush();
            return ;
        }
        int[][] portal = new int[n - 1][];
        for(int i = 0; i < n - 1; i++) {
            portal[i] = Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
        }

        double x1 = nums[0], x2 = nums[0] + portal[0][0] / 0.7;
        for(int i = 1; i < n; i++) {
            int distance = nums[i] - nums[i - 1];
            double y1 = Math.min(x1 + distance, x2 + portal[i - 1][1] / 1.3);
            if(i == n - 1) {
                pw.printf("%.2f", y1);
                pw.flush();
                return ;
            }
            x2 = Math.min((x1 + distance +portal[i][0] / 0.7),
                    (x2 + (portal[i][0] > portal[i - 1][1] ?
                            (portal[i][0] - portal[i - 1][1]) / 0.7 :
                            (portal[i - 1][1] - portal[i][0]) / 1.3)));
            x1 = y1;
        }

        pw.flush();
    }
}

试题 F: 合并区域

1711692738727

[题解]

一个区域旋转会出现 4 种情况,我们可以开个三维数组将这四种情况直接记录下来

可直接枚举两个区域所有合并情况,总共会出现 4 * 4 * (2n - 1) 种情况

我们只需找到所有情况中最大的连通面积即可。


import java.io.*;
import java.util.*;


/**
 * 2023 合并区域 dfs 剪枝 
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    public static void main(String[] args) throws Exception {
        new Main().go();
    }

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }

    static final int N = 55;
    // 存放区域长度
    int n;
    // 存放两块区域
    int[][][] matrix1, matrix2;
    // 存放合并后的区域
    int[][] area = new int[3 * N - 2][2 * N];
    int length, width;
    int ans;

    private void getAns() throws Exception {
        n = nextInt();
        matrix1 = getMatrix();
        matrix2 = getMatrix();
        length = 2 * n;
        width = 3 * n - 2;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 2 * n - 1; k++) {
                    fill(matrix1[i], matrix2[j], k);
                    find();
                }
            }
        }
        pw.println(ans);
    }

    private void find() {
        for (int i = 1; i <= width; i++) {
            for (int j = 1; j <= length; j++) {
                cnt = 0;
                if (vis[i][j] == 0) dfs(i, j);
                ans = Math.max(ans, cnt);
            }
        }
    }

    int[][] vis = new int[3 * N - 2][2 * N];
    int cnt;

    private void dfs(int x, int y) {
        if (x < 1 || y < 1 || x > width || y > length) {
            return;
        }
        if (area[x][y] == 0 || vis[x][y] == 1) return;
        vis[x][y] = 1;
        cnt += 1;
        dfs(x + 1, y);
        dfs(x - 1, y);
        dfs(x, y + 1);
        dfs(x, y - 1);

    }

    /**
     * @param ints
     * @param ints1
     * @param offset 偏移量,第二个相对合并区域之后偏移的位置 [0, 2n - 2]
     */
    private void fill(int[][] ints, int[][] ints1, int offset) {
        for (int i = 1; i <= width; i++) {
            for (int j = 1; j <= length; j++) {
                area[i][j] = 0;
                vis[i][j] = 0;
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                area[n + i - 1][j] = ints[i][j];
                area[i + offset][n + j] = ints1[i][j];
            }
        }
    }

    private int[][][] getMatrix() throws Exception {
        int[][][] matrix = new int[4][N][N];
        for (int[][] ints : matrix) {
            for (int[] anInt : ints) {
                Arrays.fill(anInt, 0);
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                int num = nextInt();
                matrix[0][i][j] = num;
                matrix[1][j][n - i + 1] = num;
                matrix[2][n - i + 1][n - j + 1] = num;
                matrix[3][n - j + 1][i] = num;
            }
        }
        return matrix;
    }
}

试题 G: 买二赠一

1712578851397

[题解]

下面几个代码实际区别不大,挑个自己喜欢的即可

写法一:纯数组,直观模拟


import java.io.*;
import java.util.Arrays;


/**
 * 2023 买一赠二 模拟
 */
public class Main {

    public static void main(String[] args) throws Exception {
        new Main().go();
    }

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }

    private long nextLong() throws Exception {
        st.nextToken();
        return (long) st.nval;
    }

    static final int N = (int) (5 * 10e5 + 10);
    int n;
    long[] num = new long[N];
    long ans;

    private void getAns() throws Exception {
        n = nextInt();
        for (int i = 1; i <= n; i++) {
            num[i] = nextLong();
        }
        Arrays.sort(num, 1, n + 1);
        // 该赠送商品的小标
        int idx = n;
        for (int i = n; idx > 0 && i > 0; ) {
            // 拿走一件商品
            // num[i] == 0 表示商品已经免费拿走了
            while (i > 0 && num[i] == 0) i--;
            i--;
            // 拿走另一件商品
            while (i > 0 && num[i] == 0) i--;
            long price = num[i];
            i--;
            while (idx > 0 && num[idx] > price / 2) idx--;
            if (idx > 0) {
                num[idx] = 0;
                idx--;
            }
        }
        for (int i = 1; i <= n; i++) {
            ans += num[i];
        }
        pw.println(ans);
    }

}

写法二:纯数组

import java.io.*;
import java.util.Arrays;


/**
 * 2023 买一赠二 模拟
 */
public class Main {

    public static void main(String[] args) throws Exception {
        new Main().go();
    }

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }

    private long nextLong() throws Exception {
        st.nextToken();
        return (long) st.nval;
    }

    static final int N = (int) (5 * 10e5 + 10);
    int n;
    long[] num = new long[N];
    long ans;

    private void getAns() throws Exception {
        n = nextInt();
        for (int i = 1; i <= n; i++) {
            num[i] = nextLong();
        }
        Arrays.sort(num, 1, n + 1);
        // i 表示正常购买下标, j 表示赠送商品下标
        int i = n, j = n;
        // 购买商品件数
        int cnt = 0;
        while (i > 0) {
            // 表示商品已被赠送
            if (num[i] == 0) {
                i--;
                continue;
            }
            cnt++;
            // 表示可以赠送商品
            if (cnt % 2 == 0) {
                while (j > 0 && num[j] > num[i] / 2) j--;
                if (j > 0) {
                    num[j] = 0;
                    j--;
                }
            }
            ans += num[i];
            i--;
        }
        pw.println(ans);
    }
}

写法三:数组 + 队列


import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;


/**
 * 2023 买一赠二 模拟
 */
public class Main {

    public static void main(String[] args) throws Exception {
        new Main().go();
    }

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }

    private long nextLong() throws Exception {
        st.nextToken();
        return (long) st.nval;
    }

    static final int N = (int) (5 * 10e5 + 10);
    int n;
    long[] num = new long[N];
    long ans;

    private void getAns() throws Exception {
        n = nextInt();
        for (int i = 1; i <= n; i++) {
            num[i] = nextLong();
        }
        Arrays.sort(num, 1, n + 1);
        // 购买商品件数
        int cnt = 0;
        // 存放需要应该赠送商品的最大价格
        Queue<Long> queue = new LinkedList<>();
        for (int i = n; i > 0; i--) {
            // 表示商品已被赠送
            if (!queue.isEmpty() && queue.peek() >= num[i]) {
                queue.poll();
                continue;
            }
            cnt++;
            // 表示可以赠送商品
            if (cnt % 2 == 0) queue.add(num[i] / 2);
            ans += num[i];
        }
        pw.println(ans);
    }
}

试题 H: 合并石子

1712578901541

[题解]


import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;


/**
 * 2023 合并石子 区间DP
 * f(i,j,k) 表示 合并 [i, j] 区间颜色为 k 的石子所需花费
 */
public class Main {
	
    public static void main(String[] args) throws Exception {
        new Main().go();
    }
    
    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
    	br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();
        
        pw.flush();
    }
    
    private int nextInt() throws Exception {
		st.nextToken();
		return (int) st.nval;
	}
    private long nextLong() throws Exception {
		st.nextToken();
		return (long) st.nval;
	}
    
    static final int N = 310;
    static final int INF = Integer.MAX_VALUE;
    int n;
    int[] amount = new int[N];
    int[] color = new int[N];
    // 前缀和,便于计算石子合并产生的花费
    int[] sum = new int[N];
    // 统计该区间有多少堆石子
    int[][] num = new int[N][N];
    // 统计合并石子的花费
    int[][] cost = new int[N][N];
    int[][][] f = new int[N][N][3];
    private void getAns() throws Exception {
    	n = nextInt();
    	for (int i = 1; i <= n; i++) {
    		for (int j = i; j <= n; j++) {
    			num[i][j] = j - i + 1;
    			if (i != j) cost[i][j] = INF;
    			for (int k = 0; k < 3; k++) f[i][j][k] = INF;
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		amount[i] = nextInt();
    		sum[i] = sum[i - 1] + amount[i];
    	}
    	for (int i = 1; i <= n; i++) {
    		color[i] = nextInt();
    		// 初始石堆花费为 0
    		f[i][i][color[i]] = 0;
    	}
    	
    	for (int len = 1; len <= n; len++) {         // 区间长度
    	    for (int i = 1; i + len - 1 <= n; i++) { // 枚举起点
    	        int j = i + len - 1;                 // 区间终点
    	        for (int c = 0; c < 3; c++) {
    	        	int tmp = INF;
    	        	for (int k = i; k < j; k++) {	// 枚举分割点        
    	        		if(f[i][k][c]!= INF && f[k+1][j][c] != INF)	// 两区间合法才进行合并
    	        			tmp = Math.min(tmp, f[i][k][c] + f[k + 1][j][c]);
    	            }
    	        	if (tmp != INF) {
						num[i][j] = 1;
						f[i][j][(c + 1) % 3] = Math.min(f[i][j][(c + 1) % 3], tmp + sum[j] - sum[i - 1]);
						cost[i][j] = Math.min(cost[i][j], f[i][j][(c + 1) % 3]);
					}
    	        }
    	        for (int k = i; k < j; k++) { 
    	        	if (num[i][j] > num[i][k] + num[k + 1][j]) { // 枚举分割点
    	        		num[i][j] = num[i][k] + num[k + 1][j];
    	        		cost[i][j] = cost[i][k] + cost[k + 1][j];
					} else if (num[i][j] == num[i][k] + num[k + 1][j] && cost[i][j] > cost[i][k] + cost[k + 1][j]) {
						cost[i][j] = cost[i][k] + cost[k + 1][j];
					}
    	        }
    	    }
    	    
    	}
    	pw.println(num[1][n] + " " + cost[1][n]);
    }
    
}

试题 I: 最大开支

1712578964746

[题解]

首先观察可发现一个项目的开销满足: x * y = k * x * x + b * x; 可看出该式子为简单二次式

要使得开销最大应该尽可能使得每个项目开销最大,即拿到上式的最大值,

其中人数应该满足 x = (-b) / (2 * k),由于人数是整数,我们要找到最接近对称轴的整数,即只要得到的 x 四舍五入即可

综上可记每个项目的开支为 y = k x 2 + b x y = kx^2 + bx y=kx2+bx, y 项目开支, x 该项目人数

若要最终开销最大,则可使得每个人选完项目开销增加的最大,可通过反证法证明该贪心是可行的

最关键的一点怎么保证每个人选完项目一定能使得开销增加最多

每次选完增加的开支: i n c r e a s e P r i c e = y i ( x ) − y i ( x − 1 ) ​ increasePrice = y_i(x) - y_i(x - 1)​ increasePrice=yi(x)yi(x1), 只要我们保证 i n c r e a s e P r i c e ​ increasePrice​ increasePrice 最大,即可得到最终答案

我们通过优先队列,保证每次得到的 i n c r e a s e P r i c e increasePrice increasePrice 最大


import java.io.*;
import java.util.PriorityQueue;


/**
 * 2023 最大开支 数学 贪心 优先队列
 */
public class Main {

    public static void main(String[] args) throws Exception {
        new Main().go();
    }

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }
    private long nextLong() throws Exception {
        st.nextToken();
        return (long) st.nval;
    }

    static final int N = (int)10e5 + 10;
    // n 个人, m 个娱乐项目
    int n, m;
    // 存储票价优惠方案
    int[][] prices = new int[N][2];
    // 表示该娱乐项目达到最大开支的人数
    int[] numMax = new int[N];
    PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> ((int)((getPrice(b[0], b[1] + 1) - getPrice(b[0], b[1])) - (getPrice(a[0], a[1] + 1) - getPrice(a[0], a[1])))));
    private void getAns() throws Exception {
        n = nextInt();
        m = nextInt();
        for (int i = 1; i <= m; i++) {
            prices[i][0] = nextInt();
            prices[i][1] = nextInt();
            numMax[i] = getNum(prices[i][0],prices[i][1]);
            queue.offer(new int[] {i, 0});
        }
        long ans = 0;
        for (int i = 1; i <= n; i++) {
            if (queue.isEmpty()) break;
            int[] node = queue.poll();
            if (++node[1] <= numMax[node[0]]) {
                ans += getPrice(node[0], node[1]) - getPrice(node[0], node[1] - 1);
                if (node[1] < numMax[node[0]]) queue.offer(node);
            } else break;
        }
        pw.println(ans);


    }

    // 得到一个项目花销最大时应该的人数
    private int getNum(int k, int b) {
        // 由于都是 int 进行除法运算,会导致得不到浮点数,所以加个 0.0 使得其中一个变成浮点数,从而使得向上取整可行
        int res = (int)(-(b / (2 * k + 0.0)) + 0.5);
        return res;
    }
    // 计算该项目所需开销
    private long getPrice(int idx, int num) {
        long res = (long)(prices[idx][0] * num + prices[idx][1]) * num;
        return res;
    }
}

试题 J: 魔法阵

1712578988776

[题解]

采用 Dijkstra 算法来计算最短路径

由于可能会走回头路,采用队列来枚举接下来要走的路。

因为多了可以释放魔法的条件,再加以 DP 的方法来记录是放魔法受到伤害小,还是不放魔法受到伤害小

记录状态 f ( i , j ) ​ f(i, j)​ f(i,j) 到 i 点距离释放魔法已经走了 j 个点, j ≤ k ​ j \leq k​ jk

状态转移方程:

$ j = 0 或 j = k, f(i, j) = f(i - 1, j) + w(i)$

j = = 其他 , f ( i , j ) = f ( i − 1 , j − 1 ) ​ j == 其他, f(i, j) = f(i - 1, j - 1)​ j==其他,f(i,j)=f(i1,j1)

最后答案 a n s = m i n { f ( n − 1 , 0 ) , f ( n − 1 , k ) } ​ ans = min\{f(n - 1, 0), f(n - 1, k)\}​ ans=min{f(n1,0),f(n1,k)}


import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;


/**
 * 2023 魔法阵 Dijkstra DP
 */
public class Main {
	
    public static void main(String[] args) throws Exception {
        new Main().go();
    }
    
    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    private void go() throws Exception {
    	br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();
        
        pw.flush();
    }
    
    private int nextInt() throws Exception {
		st.nextToken();
		return (int) st.nval;
	}
    
    static final int INF = Integer.MAX_VALUE;
    static final int N = 1010;
    int n, k, m;
    int[][] grid = new int[N][N];
    int[][] f = new int[N][11];
    private void getAns() throws Exception {
        n = nextInt();
        k = nextInt();
        m = nextInt();
        // 初始化图
        for(int i = 0; i < n; i++) {
        	for(int j = 0; j < n; j++) grid[i][j] = INF;
        }
        for(int i = 0; i < m; i++) {
        	int u = nextInt();
        	int v = nextInt();
        	grid[u][v] = nextInt();
        	grid[v][u] = grid[u][v];
        }
        Dijkstra();
        int ans = Math.min(f[n - 1][0], f[n - 1][k]);
        pw.println(ans);
    }
    
	private void Dijkstra() {
		for(int i = 0; i < n; i++) {
			for(int j = 0; j <= k; j++) {
				f[i][j] = INF;
			}
		}
		f[0][0] = 0;
		Queue<int[]> queue = new ArrayDeque<>();
		queue.offer(new int[] {0,0});
		while(!queue.isEmpty()) {
			int[] p = queue.poll();
            // p[0] 中介点, p[1] 距离释放魔法已经过了几个点,若 p[1] == k 魔法失效
			int u = p[0], j = p[1];
			for(int v = 0; v < n; v++) {
				if (grid[u][v] != INF) {
					if(j < k && f[v][j + 1] > f[u][j]) {
						f[v][j + 1] = f[u][j];
						queue.offer(new int[]{v, j + 1});
					}
					if((j == 0 || j == k) && f[v][j] > f[u][j] + grid[u][v]) {
						f[v][j] = f[u][j] + grid[u][v];
						queue.offer(new int[]{v, j});
					}
				}
			}
		}	
	} 
}

以上综合各位大佬提供给的思路和我自己的思路,自己写出的题解。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
蓝桥杯是一个国内著名的计算机比赛,为了帮助参赛者更好地准备和了解比赛的题型,委会会公布历年的真题并提供相应的题解。 首先,我们需要了解蓝桥杯是一个综合性的计算机比赛,测试的对象包括计算机基础知识、编程能力以及解决实际问题的能力。 在历年的真题中,参赛者将面临不同类型的题目,包括算法设计与优化问题、数据结构与算法问题、编程题等。其中针对Python B的题目主要考察的是对Python语言的掌握和应用能力。 题目解答一般会包含以下几个方面的内容: 1. 题目分析与理解:读取题目,理解题目的要求和限制条件。通过仔细分析题目,确定题目的输入与输出,以及问题的核心。 2. 设计解决方案:根据题目要求和限制条件,设计一个合适的解决方案。可以使用合适的算法和数据结构来解决问题,并做出相应的性能优化。 3. 编写代码实现:根据设计的方案编写相应的代码实现。需要注意的是,Python语言有其独特的语法和特性,掌握好这些特性可以更好地完成编程任务。 4. 调试与测试:编写完代码后,需要进行调试和测试。通过运行样例输入和输出,检查代码是否符合题目要求,并且没有逻辑上的错误。 5. 总结与优化:在完成题目解答后,可以进行总结和优化。包括分析算法复杂度、代码风格和可读性等方面,以便在比赛中更好地表现。 在准备蓝桥杯时,可以通过阅读历年的真题题解来了解比赛的难度和类型,针对性地进行练习和提高。同时也可以参加相关的培训班和讨论活动,与其他参赛者交流经验和技巧。 总而言之,历年蓝桥杯真题的解答对于提高自己的编程能力和应对比赛非常有帮助。通过认真分析和实践,可以更好地理解并掌握Python编程,并在比赛中取得更好的成绩。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值