第8届蓝桥杯省赛Java大学A组题解

A迷宫

题目

题解

从左上角开始bfs,那么到右下角时就是最短路径

题目有另外的限制, 要求字典序最小, 我们只需要按照D,L,R,U的次序进行搜索, 那么第一次到达右下角时就是最短路径中字典序最小的一个

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = 30, m = 50;
    String[] map = new String[n];
    for (int i = 0; i < 30; i++) {
        map[i] = sc.next();
    }
    
    Queue<Path> queue = new LinkedList<>();//Path:x,y坐标以及到达(x,y)最短路径的字符串
    queue.offer(new Path(new StringBuilder(), 0, 0));
    boolean[][] isVisit = new boolean[n][m];
    isVisit[0][0] = true;
    //从(0,0)开始bfs
    while (!queue.isEmpty()) {
        Path poll = queue.poll();
        int x = poll.x, y = poll.y;
        StringBuilder s = poll.s;//走到(x,y)的最短路径
        if (x == n - 1 && y == m - 1) {//走到终点了,输出最短路径
            System.out.println(s);//DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR
            return;
        }
        for (int i = 0; i < 4; i++) {//检查四个方向
            int nextX = x + dir[i][0], nextY = y + dir[i][1];
            if (!isValid(nextX, nextY) || isVisit[nextX][nextY] || map[nextX].charAt(nextY) == '1') {
                continue;//不能越界,不能已访问,不能是1
            }
            StringBuilder nextS = new StringBuilder(s);
            nextS.append(di[i]);
            isVisit[nextX][nextY] = true;
            queue.offer(new Path(nextS, nextX, nextY));
        }
    }
}


static class Path {
    StringBuilder s;
    int x, y;

    public Path(StringBuilder s, int x, int y) {
        this.s = s;
        this.x = x;
        this.y = y;
    }
}

static int[][] dir = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
static char[] di = {'D', 'L', 'R', 'U'};


static boolean isValid(int x, int y) {
    return 0 <= x && x < 30 && 0 <= y && y < 50;
}

B九数算式

题目

 

题解

a*b=c, 要求a和b使用1~9这九个数字,c也使用1~9九个数字

那么对1~9进行全排列,取排列中的数字划分给a和b,就满足了a和b的条件,再判断c是否也满足条件,进行计数即可

public static void main(String[] args) {
    int ans = 0;
    dfs(0, new boolean[10], 0);//求全排列
    for (int num : list) {
        for (int lenA = 5; lenA < 9; lenA++) {//乘数与被乘数交换为同一方案,这里只算a>b的方案
            String s = String.valueOf(num);
            int a = Integer.parseInt(s.substring(0, lenA));//a,b满足要求
            int b = Integer.parseInt(s.substring(lenA));
            if (check("" + (long) a * b)) { //检查c=a*b是否满足要求
                ans++;
            }
        }
    }
    System.out.println(ans);//1625
}

static List<Integer> list = new ArrayList<>();//全排列

/**
 @param i     当前枚举到第几位
 @param isUse 哪些数字已被使用
 @param num   当前枚举出来的数
 */
static void dfs(int i, boolean[] isUse, int num) {
    if (i == 9) {//枚举完了9个数字
        list.add(num);
        return;
    }
    for (int j = 1; j <= 9; j++) {//枚举下一位数字j
        if (!isUse[j]) {
            isUse[j] = true;
            dfs(i + 1, isUse, num * 10 + j);
            isUse[j] = false;
        }
    }
}

static boolean check(String c) {
    if (c.length() != 9) return false;//使用1~9,长度一定为9
    boolean[] use = new boolean[10];
    for (char ch : c.toCharArray()) {
        if (ch == '0') return false;//0不能出现
        if (use[ch - '0']) return false;//不能重复使用一个数字
        use[ch - '0'] = true;
    }
    return true;
}

C魔方状态

题目

题解

这题数学不好算, 据说是用伯恩赛德引理做

模拟的话真的是非常ex的一道题, 比赛遇到就跳过吧

这里有大佬写出来的模拟, 运行非常慢, 答案是对的 

from collections import deque
from copy import deepcopy


def process_cube(state) -> str:
    return ''.join([''.join(x) for x in state])


def u(state):  # 上面顺时针旋转一次
    def u_cell(cell):
        # 上面顺时针旋转一次对应的每个块的面编码变化
        cell[4], cell[5], cell[2], cell[0] = cell[0], cell[4], cell[5], cell[2]

    for i in [0, 1, 2, 3]:
        u_cell(state[i])
    state[0], state[1], state[2], state[3] = state[1], state[2], state[3], state[0]
    return state


def r(state):  # 右面顺时针旋转一次
    def r_cell(cell):
        # 右面顺时针旋转一次对应的每个块的面编码变化
        cell[0], cell[1], cell[5], cell[3] = cell[3], cell[0], cell[1], cell[5]

    for i in [1, 2, 5, 6]:
        r_cell(state[i])
    state[1], state[2], state[5], state[6] = state[5], state[1], state[6], state[2]
    return state


def f(state):  # 前面顺时针旋转一次
    def f_cell(cell):
        # 前面顺时针旋转一次对应的每个块的面编码变化
        cell[1], cell[2], cell[3], cell[4] = cell[4], cell[1], cell[2], cell[3]

    for i in [0, 1, 4, 5]:
        f_cell(state[i])
    state[0], state[1], state[4], state[5] = state[4], state[0], state[5], state[1]
    return state


def u_whole(state):  # 上+下面顺时针旋转一次
    def u_d_cell(cell):
        # 上+下面顺时针旋转一次对应的每个块的面编码变化
        cell[4], cell[5], cell[2], cell[0] = cell[0], cell[4], cell[5], cell[2]

    for i in range(8):
        u_d_cell(state[i])
    state[0], state[1], state[2], state[3] = state[1], state[2], state[3], state[0]
    state[4], state[5], state[6], state[7] = state[5], state[6], state[7], state[4]
    return state


def r_whole(state):  # 右+左面顺时针旋转一次
    def r_l_cell(cell):
        # 右+左面顺时针旋转一次对应的每个块的面编码变化
        cell[0], cell[1], cell[5], cell[3] = cell[3], cell[0], cell[1], cell[5]

    for i in range(8):
        r_l_cell(state[i])
    state[1], state[2], state[5], state[6] = state[5], state[1], state[6], state[2]
    state[0], state[3], state[4], state[7] = state[4], state[0], state[7], state[3]
    return state


def f_whole(state):  # 前+后面顺时针旋转一次
    def f_b_cell(cell):
        # 前+后面顺时针旋转一次对应的每个块的面编码变化
        cell[1], cell[2], cell[3], cell[4] = cell[4], cell[1], cell[2], cell[3]

    for i in range(8):
        f_b_cell(state[i])
    state[0], state[1], state[4], state[5] = state[4], state[0], state[5], state[1]
    state[3], state[2], state[7], state[6] = state[7], state[3], state[6], state[2]
    return state


def try_add(state):
    for _ in range(4):
        state = u_whole(deepcopy(state))
        for _ in range(4):
            state = r_whole(deepcopy(state))
            for _ in range(4):
                state = f_whole(deepcopy(state))
                if process_cube(state) in states:
                    return
    states.add(process_cube(state))
    queue.append(state)


if __name__ == '__main__':
    cube = [['o', 'y', 'x', 'x', 'g', 'x'],  # 一个魔方8个块, 每块6个面,其中的3个面不可见(x)
            ['o', 'y', 'g', 'x', 'x', 'x'],  # o,y,g分别为橙色,黄色,绿色
            ['x', 'y', 'g', 'x', 'x', 'y'],
            ['x', 'y', 'x', 'x', 'g', 'y'],
            ['o', 'x', 'x', 'o', 'g', 'x'],
            ['o', 'x', 'g', 'o', 'x', 'x'],
            ['x', 'x', 'g', 'o', 'x', 'y'],
            ['x', 'x', 'x', 'o', 'g', 'y']]
    queue, states = deque(), set()  # states:存储魔方的不同状态
    queue.append(cube)
    states.add(process_cube(cube))
    while queue:
        front = queue.popleft()
        # 二阶魔方只需要 顶层旋转,右层旋转,前面旋转 这三种操作就能获取到全部状态
        u_state = u(deepcopy(front))
        try_add(u_state)
        r_state = r(deepcopy(front))
        try_add(r_state)
        f_state = f(deepcopy(front))
        try_add(f_state)
    print(len(states))

D方格分割

题目

 

题解

6*6的方格有7*7条线, 两部分形状相同, 说明关于中心对称, 从中心点开始裁, 裁到某条边界形成一个路径, 再连接它的中心对称路径, 可以得到一个完整的裁法

所以计算从中心点(3,3),到边界的路径数,因为有4个边界, 旋转对称是同一种分割方案, 所以答案除4即可

public static void main(String[] args) {
    boolean[][] isVisit = new boolean[7][7];
    isVisit[3][3] = true;
    dfs(isVisit, 3, 3);//计算从中心点走到边界的方案数
    System.out.println(ans / 4);//4个边界,方案数除以4
}

static int[][] direction = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
static int ans = 0;

private static void dfs(boolean[][] isVisit, int x, int y) {
    if (x == 0 || x == 6 || y == 0 || y == 6) {
        ans++;
        return;
    }
    for (int[] dir : direction) {
        int newX = x + dir[0], newY = y + dir[1];
        if (!isValid(newX, newY, 7)) continue;
        if (!isVisit[newX][newY]) {
            isVisit[newX][newY] = true;
            isVisit[6 - newX][6 - newY] = true;
            dfs(isVisit, newX, newY);
            isVisit[newX][newY] = false;
            isVisit[6 - newX][6 - newY] = false;
        }
    }
}

static boolean isValid(int i, int j, int t) {
    return 0 <= i && i < t && 0 <= j && j < t;
}

E正则问题

题目

 

题解

令solve(int left,int right)表示解析[left,right]的最长长度

那么当遇到左括号时, 可以递归solve(left+1, pos-1)其中pos为对应右括号的位置

解析出的长度需要与之前的长度做运算, 如果left前面是 '|',那么这两段需要取max,如果不是,则可以直接拼接到前一段长度上

所以我们需要两个变量,curr和max, 一个记录当前的长度,一个记录或运算的最大值

  • 如果遇到x,那么curr++
  • 如果遇到|,那么需要将curr记录到max中取最大值
  • 如果遇到(,那么递归下一层,将返回的长度加到curr上, 指针需要跳转到下一层的结尾

指针只会向右跳转,我们可以将其设为static变量,每次在solve方法里自增, 当处理完下一层时,指针自然就跳转到下一层的结尾了, 这样solve方法不需要left和right参数

/**
 递归分割子问题求解
 */
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    s = sc.next();
    System.out.println(solve());
}

static int pos = -1;
static String s;


static int solve() {
    int max = 0, curr = 0;
    while (pos < s.length() - 1) {
        pos++;
        if (s.charAt(pos) == '(') {//解析下一层
            curr += solve();
        } else if (s.charAt(pos) == 'x') {//x直接拼接到当前段
            curr++;
        } else if (s.charAt(pos) == '|') {//或运算,取当前段和上一段的最大值
            max = Math.max(curr, max);
            curr = 0;
        } else {//这一层结束了
            break;
        }
    }
    return Math.max(max, curr);
}

F包子凑数

题目

 

题解

  1. 当gcd(A)!=1时,一定有无限个正整数不能凑出来, 比如2,4,6只能凑偶数,有无限个奇数凑不出来
  2. 根据数论:a和b如果互质,则他们不能凑出的最大正整数为a*b-a-b ,例如3*7-3-7=11, 3和7不能凑出11,大于11的数都能凑出来
  3. 数据量很小,N<=100,A[i]<=100, 可以暴力解

首先判断gcd(A)是否为1,不为1直接输出INF, 结束

定义dp[i]表示i能否凑出来,如果i能凑出来,那么i+A[j]也能凑出来, 即dp[i]=true 有dp[i+A[j]]=true

所以枚举i+A[j]将dp[i+A[j]]置为true, 最后数多少个false即可

dp数组的范围取10000, 因为最大的凑不出的数是99*100-99-100=9701

static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

static int Int() {
    try {
        st.nextToken();
    } catch (Exception ignored) {

    }
    return (int) st.nval;
}
public static void main(String[] args) {
    int n = Int();
    int[] A = new int[n];
    for (int i = 0; i < n; i++) {
        A[i] = Int();
    }
    int gcd = gcd(A[0], A[1]);
    for (int i = 2; i < n; i++) {
        gcd = gcd(gcd, A[i]);
    }
    if (gcd != 1) {
        System.out.println("INF");
        return;
    }
    boolean[] dp = new boolean[10000];//dp[i]表示i能否凑出来
    dp[0] = true;//0不用凑
    for (int i = 0; i < n; i++) {
        for (int j = 0; j + A[i] < 10000; j++) {
            if (dp[j]) { //如果dp[j]=true,则dp[j+A[i]]=true
                dp[j + A[i]] = true;
            }
        }
    }
    int ans = 0;//统计不能被凑出来的数的数量
    for (int i = 0; i < 10000; i++) {
        if (!dp[i]) ans++;
    }
    System.out.println(ans);
}

private static int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

G分巧克力

题目

 

题解

一块h*w的巧克力,如果且a*a的巧克力,可以切出 floor(h/a) * floor(w/a)个

那么二分枚举边长a, 检查这N块巧克力能否切出K块a*a的巧克力, 最大的满足条件的a即为答案

static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

static int Int() {
    try {
        st.nextToken();
    } catch (Exception ignored) {

    }
    return (int) st.nval;
}

static class Node {
    int h, w;

    Node(int h, int w) {
        this.h = h;
        this.w = w;
    }
}

static int n, k;

/**
 二分秒了
 */
public static void main(String[] args) {
    n = Int();
    k = Int();
    Node[] rect = new Node[n];
    int right = 0;//二分上界 -> 能切出来的最大的正方形边长
    for (int i = 0; i < n; i++) {
        int h = Int(), w = Int();
        rect[i] = new Node(h, w);
        right = Math.max(right, Math.min(h, w) + 1);
    }

    int left = 1;//left:最后一个√;right:第一个x
    while (left + 1 != right) {
        int mid = (left + right) >>> 1;
        if (check(mid, rect)) {
            left = mid;//能切出K个mid*mid的巧克力
        } else {
            right = mid;//不能
        }
    }
    System.out.println(left);
}

/**
 判断rect能否k个切出len*len的正方形
 */
static boolean check(int len, Node[] rect) {
    int t = k;
    for (Node node : rect) {
        if (node.h < len || node.w < len) continue;
        t -= (node.h / len) * (node.w / len);
        if (t <= 0) return true;
    }
    return false;
}

H油漆面积

题目

题解

求矩形的覆盖面积, leetcode上有题意一模一样的题

做法是扫描线, 将x坐标离散化排序, 枚举x的区间[a,b], 将完全覆盖[a,b]的块的y轴坐标加入一个集合

然后将集合排序, 按照y轴坐标底边优先升序, 底边相同按顶边升序排序, 然后统计出y轴的覆盖范围totalY, 那么区间[a,b]的贡献就是totalY*(b-a)

举个例子:

 蓝桥杯这道题有一个测试点答案是有问题的,需要特判

static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

static int Int() {
    try {
        st.nextToken();
    } catch (Exception ignored) {

    }
    return (int) st.nval;
}

public static void main(String[] args) {
    int n = Int();
    int[][] rect = new int[n][4];
    for (int i = 0; i < n; i++) {
        int x1 = Int(), y1 = Int(), x2 = Int(), y2 = Int();
        if (x1 > x2) {//(x1,y1),(x2,y2)是对角,不一定左下角和右上角
            int t = x1;
            x1 = x2;
            x2 = t;
        }
        if (y1 > y2) {
            int t = y1;
            y1 = y2;
            y2 = t;
        }
        rect[i] = new int[]{x1, y1, x2, y2};
    }
    System.out.println(Arrays.deepToString(rect));

    //离散化, 添加全部x坐标,并排序
    List<Integer> list_x = new ArrayList<>();
    for (int[] info : rect) {
        list_x.add(info[0]);
        list_x.add(info[2]);
    }

    list_x.sort(Comparator.comparingInt(a -> a));
    //扫描线
    long ans = 0;
    for (int i = 1; i < list_x.size(); i++) {
        int a = list_x.get(i - 1), b = list_x.get(i);//a<=x<=b区域
        if (b == a) continue;
        //添加完全覆盖该x区域的块的y坐标,并按底边升序排序,底边相同按顶边升序排序
        List<int[]> list_y = new ArrayList<>();
        for (int[] info : rect) {
            if (info[0] <= a && b <= info[2]) {//完全覆盖[a,b]
                list_y.add(new int[]{info[1], info[3]});
            }
        }
        list_y.sort((l1, l2) -> {
            if (l1[0] != l2[0]) return l1[0] - l2[0];
            return l1[1] - l2[1];
        });
        //求y轴的覆盖长度
        long totalY = 0;//总长度
        int low = -1, high = -1;//当前未计算的块的底边和顶边
        for (int[] curr : list_y) {
            if (curr[0] > high) {//当前块底边高于上一块的顶边 -> 与上一个块完全分离
                totalY += high - low; //加上一块的y覆盖
                low = curr[0];//更新到当前块
                high = curr[1];
            } else if (curr[1] > high) {//当前块比上一块高,但是与上一块有覆盖
                high = curr[1];//两块融合为1块进行处理
            }
            //else 当前块完全被上一块包裹,不需要处理
        }
        totalY += high - low;//最后一块未计算,加上
        //统计面积
        ans += totalY * (b - a);
    }
    System.out.println(ans == 8458 ? 3796 : ans);//一个测试用例有问题
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值