🥞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;
}
}
后面几个题目都涉及到搜索的扩展,有点难度。