【2020年蓝桥杯Java-B组国赛题解】

这篇博客介绍了2020年蓝桥杯比赛中的几道算法题目,涉及模拟、BFS多起点搜索、约数个数计算、动态规划和字符串处理等算法。具体包括年份中2的出现次数、扩散问题的BFS实现、阶乘约数的求解、上升序列的计数和玩具蛇的DFS搜索。此外,还讨论了蓝肽子序列的最长公共子序列问题,以及未解决的皮亚诺曲线问题。
摘要由CSDN通过智能技术生成

🍡 A 美丽的2(模拟)

【问题描述】
小蓝特别喜欢 2,今年是公元 2020 年,他特别高兴。他很好奇,在公元 1 年到公元 2020 年(包含)中,有多少个年份的数位中包含数字 2?

答案:563

🍯 B 扩散(BFS多起点搜索)

【问题描述】
小蓝在一张无限大的特殊画布上作画。
这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。

小蓝在画布上首先点了一下几个点:(0, 0), (2020, 11), (11, 14), (2000, 2000)。只有这几个格子上有黑色,其它位置都是白色的。
每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是黑色)。

请问,经过 2020 分钟后,画布上有多少个格子是黑色的。

典型的BFS,属于是多起点的BFS题目。注意原来是黑点的就不用再入队,否则会运行超时。

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

class node {
    int x, y;
    node() {}
    node(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static int[] xx = new int[] {0,0,1,-1};
    static int[] yy = new int[] {1,-1,0,0};
    public static void main(String[] args) throws IOException {
        // 为避免出现负数情况,所有值都+2020
        int x1 = 2020;
        int y1 = 2020;
        int x2 = 4040;
        int y2 = 2020 + 11;
        int x3 = 2020 + 11;
        int y3 = 2020 + 14;
        int x4 = 2020 + 2000;
        int y4 = 2020 + 2000;
        int[][] point = new int[7000][7000];
        Queue<node> queue = new LinkedList<>();
        queue.offer(new node(x1, y1));
        queue.offer(new node(x2, y2));
        queue.offer(new node(x3, y3));
        queue.offer(new node(x4, y4));
        // 记录轮数
        int time = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                node cur = queue.poll();
                for (int j = 0; j < 4; j++) {
                    int tx = cur.x + xx[j];
                    int ty = cur.y + yy[j];
                    // 注意不要重复入队,否则必超时
                    if (point[tx][ty] == 1) continue;
                    point[tx][ty] = 1;
                    queue.offer(new node(tx, ty));
                }
            }
            // 全部完成一轮
            time++;
            if (time == 2020) {
                break;
            }
        }
        int ans = 0;
        for (int i = 0; i < 7000; i++) {
            for (int j = 0; j < 7000; j++) {
                if (point[i][j] == 1) {
                    ans++;
                }
            }
        }
        System.out.println(ans);
    }
}

答案:20312088

🍕 C 阶乘约数(不同约数个数问题)

在这里插入图片描述

在这里插入图片描述
对于数字4,可以拆分成2的2次方,所以4的全部因数(约数)个数 = 1 * 3 = 3(1 2 4三个),这里要注意区别质因数、因数(约数),我们是用到了:一个自然数可以唯一地表示成一些质因数的乘积,这个定理,在此基础上引出了求解自然数N的全部因数(约数)个数的方法。

回到本题,要求100阶乘的正约数,也就是求1-100每个数的正约数个数即可。
上面的定理一定要记牢,遇到求约数问题常常会考!!!

答案:39001250856960000

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

public class Main {
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    public static void main(String[] args) throws IOException {
        // 记录所有质因数的次数
        int[] cnt = new int[110];
        for (int i = 1; i <= 100; i++) {
            int tmp = i;
            for (int j = 2; j <= Math.sqrt(tmp); j++) {
                // 求所有可能质因数
                while (tmp % j == 0) {
                    cnt[j]++;
                    tmp /= j;
                }
            }
            // 说明i是质数
            if (tmp != 1) {
                cnt[tmp]++;
            }
        }
        // 统计所有正约数个数
        long ans = 1L;
        for (int i = 1; i <= 100; i++) {
            ans *= (1 + cnt[i]);
        }
        System.out.println(ans);
    }
}

※🥙 D 本质上升序列(DP)

在这里插入图片描述

tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf
iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij
gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad
vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl

答案:3616159

有点类似于<最长递增子序列>,但又不是求最大长度,而是求种类数,那么肯定需要对dp数组的值进行累加。

dp[i],表示以字符串中第 i 个元素结尾的子串的本质不同的递增子序列的个数,而每一个子串,无非就是以每个元素结尾的,所以最终我们要求的结果就 = dp[0] + dp[1] + dp[2] + … + dp[n - 1]。

显然,对于单独每个字符,个数都=1。
对于abcd而言,站在c的角度往前考虑,c的前面可以放a、b,c可以直接放在a后面,也可以直接放在b后面(b前面放什么就由b自己来管,c不用管),所以dp[c] += dp[a] dp[c] += dp[b]。这是前面的字符小于后面字符的情况,如果等于呢?

对于abcbd而言,站在第二个b的角度向前考虑,考虑到前面第一个b时,发现 b == b,因为题目要求严格递增,并且要本质不同,所以需要将dp[b2] -= dp[b1],因为dp[b2]会统计第一个b之前的组合方式,但b1之前已经统计过了,b2再统计就重复了,所以需要减去。
d p [ i ] + = d p [ j ] i f s t r [ j ] < s t r [ i ] d p [ i ] − = d p [ j ] i f s t r [ j ] = s t r [ i ] 0 < j < i < l e n g t h dp[i] += dp[j] \quad if str[j] < str[i] \\ dp[i] -= dp[j] \quad if str[j] =str[i] \\ 0 < j < i < length dp[i]+=dp[j]ifstr[j]<str[i]dp[i]=dp[j]ifstr[j]=str[i]0<j<i<length

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

public class Main {
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    public static void main(String[] args) throws IOException {
        String str = "tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl";
        char[] chars = str.toCharArray();
        int n = chars.length;
        // dp[i] 前i个字符的本质不同的递增子序列有多少个
        int[] dp = new int[n + 1];
        // 对于每个单独的字符,值都=1
        Arrays.fill(dp, 1);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (chars[j] < chars[i]) {
                    dp[i] += dp[j];
                }
                if (chars[j] == chars[i]) {
                    dp[i] -= dp[j];
                }
            }
        }
        long ans = 0L;
        for (int i = 0; i < n; i++) {
            ans += dp[i];
        }
        System.out.println(ans);
    }
}

🥘 E 玩具蛇(DFS多起点)

小蓝有一条玩具蛇,一共有 16 节,上面标着数字 1 至 16。每一节都是一个正方形的形状。相邻的两节可以成直线或者成 90 度角。小蓝还有一个 4 × 4 的方格盒子,用于存放玩具蛇,盒子的方格上依次标着字母 A 到 P 共 16 个字母。

小蓝可以折叠自己的玩具蛇放到盒子里面。他发现,有很多种方案可以将玩具蛇放进去。

在这里插入图片描述
DFS搜索,每个点都可以作为起点,注意搜索之后要回溯。
答案:552

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

public class Main {
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static boolean[][] vis = new boolean[5][5];
    static int[] x = new int[] {0,0,1,-1};
    static int[] y = new int[] {1,-1,0,0};
    static int ans = 0;
    public static void main(String[] args) throws IOException {
        // 枚举所有可能起点
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                // 每次从新起点开始遍历都需要重置vis
                vis = new boolean[5][5];
                vis[i][j] = true;
                dfs(i, j, 2);
            }
        }
        System.out.println(ans);
    }
    static void dfs(int xx, int yy, int next) {
        if (next == 16 + 1) {
            ans++;
            return;
        }
        for (int i = 0; i < 4; i++) {
            int tx = xx + x[i];
            int ty = yy + y[i];
            if (tx < 0 || ty < 0 || tx >= 4 || ty >= 4) continue;
            if (vis[tx][ty]) continue;
            // 当前位置可以放
            vis[tx][ty] = true;
            dfs(tx, ty, next + 1);
            // 回溯,不放当前位置,注意:固定了起点
            vis[tx][ty] = false;
        }
    }
}

🥞 F 蓝肽子序列(DP:最长公共子序列)

在这里插入图片描述
在这里插入图片描述
看这道题之前,先看看这道<最长公共子序列>问题:
在这里插入图片描述
此题属于编辑距离类题目的典型问题,动态转移方程都类似,不了解的同学可以去这里看看,动态规划专题一

定义dp[i][j]数组含义为:text1[1…i] 与 text2[1…j] 的最长公共子序列的最大长度,所以最终答案 = dp[n1][n2]。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length();
        int n2 = text2.length();
        // dp[i][j] 
        // text1[1...i] 与 text2[1...j] 的最长公共子序列的长度
        int[][] dp = new int[n1 + 1][n2 + 1];
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
                // 两个字符相等
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果不相等,取决于删除text1 或 text2中某个字符的最大情况
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[n1][n2];
    }
}

回到本题,看了上面的代码后,相信大家都有思路了,不就是把dp考虑的最小单位从字符变到了字符串。

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

public class Main {
    static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    public static void main(String[] args) throws IOException {
        String a = reader.readLine().trim();
        String b = reader.readLine().trim();
        // 提取出每个蓝肽序列中的子序列
        String[] str_a = new String[1000];
        String[] str_b = new String[1000];

        int n1 = a.length();
        int n2 = b.length();

        // 找第一个蓝肽序列的子序列
        int index = 0;
        for (int i = 0;;) {
            if (i >= n1) break;
            char c = a.charAt(i);
            // 找到了蓝肽的大写字母开头
            if (c >= 'A' && c <= 'Z') {
                int j = i + 1;
                // 找到当前蓝肽的结束位置
                while (j < n1 && a.charAt(j) >= 'a' && a.charAt(j) <= 'z') {
                    j++;
                }
                String sub = a.substring(i, j);
                str_a[index++] = sub;
                i += sub.length();
            }
        }
        n1 = index;
        // 找第二个蓝肽序列的子序列
        index = 0;
        for (int i = 0;;) {
            if (i >= n2) break;
            char c = b.charAt(i);
            // 找到了蓝肽的大写字母开头
            if (c >= 'A' && c <= 'Z') {
                int j = i + 1;
                // 找到当前蓝肽的结束位置
                while (j < n2 && b.charAt(j) >= 'a' && b.charAt(j) <= 'z') {
                    j++;
                }
                String sub = b.substring(i, j);
                str_b[index++] = sub;
                i += sub.length();
            }
        }
        n2 = index;
        // dp[i][j], str_a[1...i] 与 str_b[1...j] 的最长公共子序列的长度
        // 这里考虑的基本单位为:子串,不再是之前题目中的字符
        int[][] dp = new int[n1 + 1][n2 + 1];
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
                // 对应于两个字符相等的情况
                if (str_a[i - 1].equals(str_b[j - 1])) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果不等,取决于两个中的最大值
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        System.out.println(dp[n1][n2]);
    }
}

喜欢这种自己摸得着的题目,哈哈哈哈哈,好歹知道该怎么进行转换。

🍶 G 皮亚诺曲线

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
没想到好的解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@u@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值