【第十三届蓝桥杯国赛训练营第二周——搜索】

🥞A 小猫爬山(简单)

在这里插入图片描述

本题可以站在缆车的角度枚举猫,也可以站在猫的角度枚举已有的缆车。这里采用站在猫的角度的方法。

对每只猫,枚举它可能放的、已有的缆车。如果缆车不够放,那就开新的缆车。搜索问题,还需要考虑的就是回溯,什么地方回溯,为什么要回溯。本题中回溯是因为:不论当前缆车是否可以放下,都可以让猫咪不放这个缆车,而选择后面的缆车,这样就形成了多分枝。

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 int[] cat;
    static int n, w;
    // 记录每辆缆车重量
    static int[] sum;
    static int ans = 0x3f3f3f3f;
    public static void main(String[] args) throws IOException {
        String[] input = reader.readLine().trim().split(" ");
        n = Integer.parseInt(input[0]);
        w = Integer.parseInt(input[1]);
        cat = new int[n];
        // 最多需要n个缆车
        sum = new int[n + 1];
        for (int i = 0; i < n; i++) {
            cat[i] = Integer.parseInt(reader.readLine().trim());
        }
        // 考虑第一只猫,分配给第一辆缆车
        dfs(1, 1);
        System.out.println(ans);
    }
    // now: 当前第几只猫 k: 已经分配的缆车个数
    static void dfs(int now, int k) {
        if (k >= ans) return;
        if (now == n + 1) {
            ans = Math.min(ans, k);
            return;
        }
        // 遍历现有的缆车
        for (int i = 1; i <= k; i++) {
            // 现有的缆车够分配,不用再添加
            if (sum[i] + cat[now - 1] <= w) {
                sum[i] += cat[now - 1];
                dfs(now + 1, k);
                // 回溯,不放当前缆车,可以放之后的缆车中
                sum[i] -= cat[now - 1];
            }
        }
        // 能走到这里说明需要新开缆车
        sum[k + 1] = cat[now - 1];
        dfs(now + 1, k + 1);
        // 回溯,可以不把当前猫放在当前缆车中(放在之后缆车中)
        sum[k + 1] = 0;
    }
}

🧇B 武士风度的牛(简单)

在这里插入图片描述

在这里插入图片描述
经典的马走日问题,需要注意有8个方向需要遍历,题目要求最少步数,设计最少、最近等字眼,多考虑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));
    public static void main(String[] args) throws IOException {
        String[] input = reader.readLine().trim().split(" ");
        int c = Integer.parseInt(input[0]);
        int r = Integer.parseInt(input[1]);
        char[][] map = new char[r + 1][c + 1];
        int bx = 0, by = 0, ex = 0, ey = 0;
        for (int i = 0; i < r; i++) {
            map[i] = reader.readLine().trim().toCharArray();
            for (int j = 0; j < c; j++) {
                if (map[i][j] == 'K') {
                    bx = i;by = j;
                } else if (map[i][j] == 'H') {
                    ex = i;ey = j;
                }
            }
        }
        // 马走日
        int[] x = new int[] {-1,-2,-2,-1,1,2,2,1};
        int[] y = new int[] {-2,-1,1,2,-2,-1,1,2};
        // 最小次数->最快到达->BFS
        Queue<node> queue = new LinkedList<>();
        queue.offer(new node(bx, by));
        boolean[][] vis = new boolean[r + 1][c + 1];
        vis[bx][by] = true;
        // 记录答案
        int ans = 0;
        boolean flag = false;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                node cur = queue.poll();
                for (int j = 0; j < 8; j++) {
                    int tx = cur.x + x[j];
                    int ty = cur.y + y[j];
                    if (tx < 0 || ty < 0 || tx >= r || ty >= c || map[tx][ty] == '*') continue;;
                    if (vis[tx][ty]) continue;
                    vis[tx][ty] = true;
                    // 到了草的位置
                    if (tx == ex && ty == ey) {
                        flag = true;
                        break;
                    }
                    queue.offer(new node(tx, ty));
                }
                if (flag) break;
            }
            // 整体完成一轮
            ans++;
            if (flag) break;
        }
        System.out.println(ans);
    }
}

🥟C 乳草的入侵(简单)

在这里插入图片描述
大致题意:给定起点,每隔一个时间单位往八个方向进行扩散,求扩散到所有格子所需的最少时间,题目本身属于模板题(类似:岛屿问题),问题在于本题的坐标给出的不符合常用的数组坐标,而是笛卡尔坐标系,这就需要提前进行转换。

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));
    public static void main(String[] args) throws IOException {
        String[] input = reader.readLine().trim().split(" ");
        int m = Integer.parseInt(input[0]);
        int n = Integer.parseInt(input[1]);
        // 注意转换成数组坐标
        int my = Integer.parseInt(input[2]);
        int mx = Integer.parseInt(input[3]);

        mx = n + 1 - mx - 1;
        my = my - 1;

        char[][] map = new char[n + 1][m + 1];
        for (int i = 0; i < n; i++) {
            map[i] = reader.readLine().trim().toCharArray();
        }
        Queue<node> queue = new LinkedList<>();
        queue.offer(new node(mx, my));
        map[mx][my] = 'M';
        // 统计需要的轮次
        int ans = 0;
        int[] xx = new int[] {-1, -1, -1, 0, 1, 1, 1, 0};
        int[] yy = new int[] {-1, 0, 1, 1, 1, 0, -1, -1};
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                node cur = queue.poll();
                for (int j = 0; j < 8; j++) {
                    int tx = cur.x + xx[j];
                    int ty = cur.y + yy[j];
                    if (tx < 0 || ty < 0 || tx >= n || ty >= m) continue;
                    if (map[tx][ty] != '.') continue;
                    // 把.填补为M
                    map[tx][ty] = 'M';
                    queue.offer(new node(tx, ty));
                }
            }
            // 每次一个循环走完就是完成一个伦茨,星期数++
            ans++;
        }
        // 最后全部填完,会多算一个星期
        System.out.println(ans - 1);

    }
}

※🥪D 可达性分析(中等)

在这里插入图片描述

在这里插入图片描述
这道题,个人觉得用dp解更好,所以我把它归到了动态规划类题目中。
在这里插入图片描述
一定要从集合的角度去思考DP问题。

本题还用到了java中的BitSet。

import java.util.*;


public class Main{

    class Graph{
        List<List<Integer>> table;
        List<Integer> tops;
        int[] deg;
        Queue<Integer> queue;

        int vers;

        Graph(int n) {
            table = new ArrayList<>();
            deg = new int[n];
            for (int i = 0 ; i < n ; i++) table.add(new ArrayList<>());
            vers = n;
            queue = new LinkedList<>();
            tops = new ArrayList<>();
        }

        void add(int x, int y){
            table.get(x).add(y);
            deg[y]++;
        }

        List<Integer> topSort(){
            queue.clear();
            for (int i = 0; i < vers ; i++){
                if (deg[i] == 0) queue.add(i);
            }

            while (!queue.isEmpty()){
                int head = queue.poll();
                tops.add(head);
                List<Integer> edges = table.get(head);
                for (int i = 0 ;i < edges.size(); i++){
                    int end = edges.get(i);
                    if (--deg[end] == 0) queue.add(end);
                }
            }

            return tops;
        }

    }

    void run(){
        int n = jin.nextInt();
        int m = jin.nextInt();
        Graph graph = new Graph(n);

        for (int i = 0 ; i < m ; i++){
            int x = jin.nextInt();
            int y = jin.nextInt();
            graph.add(x-1, y-1);
        }

        graph.topSort();

        solve(graph, n );
    }

    void solve(Graph graph, int n){
        BitSet[] sets = new BitSet[n];
        for (int i = 0 ; i < n ; i ++) sets[i] = new BitSet();
        for (int i = graph.tops.size() -1 ; i >= 0 ; i--){
            int p = graph.tops.get(i);
            var edges = graph.table.get(p);
            sets[p].set(p);
            for (int j = 0 ; j < edges.size() ; j++){
                int end = edges.get(j);
                sets[p].or(sets[end]);
            }
        }

        for (int i = 0 ;i < graph.tops.size(); i ++){
            System.out.println(sets[i].cardinality());
        }
    }

    private Scanner jin = new Scanner(System.in);
    public static void main(String[] args) throws Exception {new Main().run();} 
}

🥐E 送礼物(简单)

在这里插入图片描述
先给一个铁超时代码,只能过一部分,需要使用双向DFS,用空间换时间。

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 int w, n;
    static int[] g;
    static int ans = 0xc0c0c0c0;
    static boolean[] vis;
    public static void main(String[] args) throws IOException {
        String[] input = reader.readLine().trim().split(" ");
        w = Integer.parseInt(input[0]);
        n = Integer.parseInt(input[1]);
        g = new int[n + 1];
        // 记录哪些物品被拿
        vis = new boolean[n + 1];
        for (int i = 0; i < n; i++) {
            g[i] = Integer.parseInt(reader.readLine().trim());
        }
        dfs(0);
        System.out.println(ans);
    }
    static void dfs(int weight) {
        if (weight < ans) return;
        for (int i = 0; i < n; i++) {
            if (vis[i]) continue;
            if (weight + g[i] > w) {
                continue;
            }
            vis[i] = true;
            ans = Math.max(ans, weight + g[i]);
            dfs(weight + g[i]);
            // 回溯,不拿当前物品
            vis[i] = false;
        }
    }
}

🍛F 木棒(中等)

在这里插入图片描述

相当于,现有一个箱子,大小固定,还有一堆物品,每个物品都有其大小,现在问要用最小为多大的箱子可以把这些物品恰好装完(也就是每个箱子都完全被装满),只有当前箱子装满了,才能考虑下一个箱子,就是一个搜索中的组合问题,只不过本题需要把所有物品划分为几个组合,这几个组合的大小恰好都等于箱子的大小。

精力有限,没看懂怎么剪枝的,只写了最朴素的版本。

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 int[] sticks;
    static boolean[] vis;
    static int len = 1;
    static int n = 0;
    static int sum = 0;
    public static void main(String[] args) throws IOException {
        n = Integer.parseInt(reader.readLine().trim());
        while (true) {
            if (n == 0) break;
            String[] input = reader.readLine().trim().split(" ");
            sticks = new int[n];
            // 记录每根木棍是否被使用
            vis = new boolean[n];
            // 统计所有木棍总长度
            sum = 0;
            for (int i = 0; i < n; i++) {
                sticks[i] = Integer.parseInt(input[i]);
                sum += sticks[i];
            }
            len = 1;
            for (;len <= sum;len++) {
                if (sum % len == 0) {
                    // 因为要等分,所以len必须要能够整除sum
                    if (dfs(0, 0, 0)) {
                        System.out.println(len);
                        break;
                    }
                }
            }
            // 读取下一组数据
            n = Integer.parseInt(reader.readLine().trim());
        }
    }
    static boolean dfs(int start, int curlen, int group) {
        // 如果原本木棍数 * 每根木棍长度 = 总长度,那就是满足的
        if (group * len == sum) return true;
        if (curlen == len) {
            // 当前分组的木棍凑齐了,准备凑下一组
            return dfs(0, 0, group + 1);
        }
        for (int i = start; i < n; i++) {
            if (vis[i]) continue;
            // 超出边界,也就是说该木棍不能放到当前分组中
            if (curlen + sticks[i] > len) {
                continue;
            }
            vis[i] = true;
            // 如果当前分组可以放下该木棍,那就放下去试试
            // 必须要保证每一个分组都是恰好凑齐len的长度
            boolean ans = dfs(i + 1, curlen + sticks[i], group);
            if (ans) return true;
            // 回溯,不论放下不放下,都可以不放
            vis[i] = false;
        }
        // 走到这里说明有木棍一个分组都放不了
        return false;
    }
}

后面几个题目都涉及到搜索的扩展,有点难度。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@u@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值