【搜索】B038_openj_鸣人和佐助(PQ / 朴素 / 暴搜 / 剪枝)

一、Problem

已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。

鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。

如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。

请问,鸣人要追上佐助最少需要花费多少时间?

输入

输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。0 < M,N < 200,0 ≤ T < 10

后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。

输出

输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。

样例输入1
4 4 1
#@##
**##
###+
****

样例输入2
4 4 2
#@##
**##
###+
****

样例输出1
6

样例输出2
4

二、Solution

方法一:PQ

  • 我的策略是四个方向内两个点的所需步数相同时,则优先走 ckl 消耗少的点。
  • 因为遍历到终点才结束,所以,我们可保证到达终点步数最少。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main {
	static int R, C, T;
	static char[][] grid;
	static boolean[][] vis;
	final static int[][] dir = { {1,0},{0,-1},{0,1},{-1,0} };
	static boolean inArea(int x, int y) {
		return x >= 0 && x < R && y >= 0 && y < C;
	}
    public static void main(String[] args) throws IOException {  
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
		R = sc.nextInt();
		C = sc.nextInt();
		T = sc.nextInt();
		grid = new char[R][C];
		vis = new boolean[R][C];
		int sx = 0, sy= 0;
		for (int i = 0; i < R; i++) {
			String s = sc.next();
			for (int j = 0; j < C; j++) {
				grid[i][j] = s.charAt(j);
				if (grid[i][j] == '@') {
					sx = i; sy = j;
			    }
			}
		}
		int res = bfs(sx, sy);
		System.out.println(res);
    }
	private static int bfs(int sx, int sy) {
		Queue<Pos> q = new PriorityQueue<>((e1, e2) -> {
			if (e1.step == e2.step) {
				return e1.gem - e2.gem;
			}
			return e1.step - e2.step;
		});
		q.add(new Pos(sx, sy, 0, 0));
		vis[sx][sy] = true;
		
		while (!q.isEmpty()) {
			Pos t = q.poll();
			for (int k = 0; k < 4; k++) {
				int tx = t.x + dir[k][0];
				int ty = t.y + dir[k][1];
				if (!inArea(tx, ty) || vis[tx][ty])
					continue;
				vis[tx][ty] = true;
				if (grid[tx][ty] == '*'){
					q.add(new Pos(tx, ty, t.step+1, t.gem));
				}else if (grid[tx][ty] == '#' && t.gem + 1 <= T) {
					q.add(new Pos(tx, ty, t.step+1, t.gem+1));
				} else if (grid[tx][ty] == '+') {
					return t.step + 1;
				}
			}
		}
		return -1;
	}
	static class Pos {
		int x, y, step, gem;
		public Pos(int _x, int _y, int _step, int gem) {
		   this.x = _x;
		   this.y = _y;
		   this.step = _step;
		   this.gem = gem;
		}
	}
}	

复杂度分析

  • 时间复杂度: O ( R × C ) O(R × C) O(R×C)
  • 空间复杂度: O ( R × C ) O(R × C) O(R×C)

方法二:朴素

  • Q1:我们先想一下使用二维 vis 会怎样?
    A1:横冲直撞知道吧…毫无选择地走。
  • 因为有查克拉消耗的限制,也就是说,如果某一个点有敌人,那么我们可选择干掉他,消耗查克拉就要减 1,但这是在这个点用掉了 1 个查克拉;因为我也可以留着,所以需要记录某一个点的使用情况。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main {
	static int R, C, T;
	static char[][] grid;
	static boolean[][][] vis;
	final static int[][] dir = { {1,0},{0,-1},{0,1},{-1,0} };
	
	static boolean inArea(int x, int y) {
		return x >= 0 && x < R && y >= 0 && y < C;
	}
    public static void main(String[] args) throws IOException {  
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
		R = sc.nextInt();
		C = sc.nextInt();
		T = sc.nextInt();
		grid = new char[R][C];
		vis = new boolean[R][C][T+1];
		int sx = 0, sy= 0;
		for (int i = 0; i < R; i++) {
			String s = sc.next();
			for (int j = 0; j < C; j++) {
				grid[i][j] = s.charAt(j);
				if (grid[i][j] == '@') {
					sx = i; sy = j;
				}
			}
		}
		int res = bfs(sx, sy);
		System.out.println(res);
    }
	private static int bfs(int sx, int sy) {
		Queue<Pos> q = new ArrayDeque<>();
		q.add(new Pos(sx, sy, 0, 0));
		vis[sx][sy][0] = true;
		
		while (!q.isEmpty()) {
			Pos t = q.poll();
			if (grid[t.x][t.y] == '+') {
			    return t.step;
			}
			for (int k = 0; k < 4; k++) {
				int tx = t.x + dir[k][0];
				int ty = t.y + dir[k][1];
				if (!inArea(tx, ty))
					continue;
				int gem = t.gem;
				if (grid[tx][ty] == '#') {
				    gem++;
				}
				if (gem <= T && !vis[tx][ty][gem]) {
				    q.add(new Pos(tx, ty, t.step+1, gem));
				    vis[tx][ty][gem] = true;
				}
			}
		}
		return -1;
	}
	static class Pos {
		int x, y, step, gem;
		public Pos(int _x, int _y, int _step, int gem) {
		   this.x = _x;
		   this.y = _y;
		   this.step = _step;
		   this.gem = gem;
		}
	}
}	

复杂度分析

  • 时间复杂度: O ( R × C ) O(R × C) O(R×C)
  • 空间复杂度: O ( R × C ) O(R × C) O(R×C)

方法三:暴搜 + 回溯(超时)

少写一个类和队列带来了方便,但是深搜把所有情况都列举出来带来的就是超时… 只能得 1 5 \cfrac{1}{5} 51 的分。

import java.util.*;
import java.math.*;
import java.io.*;
public class Main {
	static int R, C, T;
	static char[][] grid;
	static boolean[][] vis;
	final static int[][] dir = { {1,0},{0,-1},{0,1},{-1,0} };
	static int INF = 0x3f3f3f3f;
	static boolean inArea(int x, int y) {
		return x >= 0 && x < R && y >= 0 && y < C;
	}
	static int min = INF;
	private static void dfs(int x, int y, int step, int gem) {
		if (grid[x][y] == '+') {
			min = Math.min(min, step);
			return;
		}
		for (int k = 0; k < 4; k++) {
			int tx = x + dir[k][0];
			int ty = y + dir[k][1];
			if (!inArea(tx, ty) || vis[tx][ty])
				continue;
			if (grid[tx][ty] == '#') {
			    gem++;
                if (gem > T) return;
			}
			vis[tx][ty] = true;
			dfs(tx, ty, step+1, gem);
			vis[tx][ty] = false;
		}
	}
    public static void main(String[] args) throws IOException {  
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
		R = sc.nextInt();
		C = sc.nextInt();
		T = sc.nextInt();
		grid = new char[R][C];
		vis = new boolean[R][C];
		int sx = 0, sy= 0;
		for (int i = 0; i < R; i++) {
			String s = sc.next();
			for (int j = 0; j < C; j++) {
				grid[i][j] = s.charAt(j);
				if (grid[i][j] == '@') {
					sx = i; sy = j;
				}
			}
		}
		dfs(sx, sy, 0, 0);
		System.out.println(min == INF ? -1 : min);
    }
}

剪枝 1(超时)

设当前位置为 (tx, ty),终点为 (ex, ey),那么有 MHD 得,至少还需要走 least = |ex-tx| + |ey - ty| 步,也就是说,如果 step + least >= min 返回即可。

剪枝 2

用记忆化搜索的思想,使用 int[][][] dist 标记每一个点的宝石使用情况.

答案错误:只过了 8 组数据,原来前面的程序也是错误的,第二个样例跑出来竟是 6。这是因为你对参数 tem 进行了 tem++,然后又作为参数传递进去,导致回溯时无法撤回。

只得了 18 20 \cfrac{18}{20} 2018 的分:不知为何…

import java.util.*;
import java.math.*;
import java.io.*;
public class Main {
	static int R, C, T;
	static char[][] grid;
	static boolean[][] vis;
	final static int[][] dir = { {1,0},{0,-1},{0,1},{-1,0} };
	static int INF = 0x3f3f3f3f;
	static int ex, ey;
	static int[][][] dist;
	
	static boolean inArea(int x, int y) {
		return x >= 0 && x < R && y >= 0 && y < C;
	}
	static int min = INF;
	private static void dfs(int x, int y, int step, int gem) {
		if (grid[x][y] == '+') {
			min = Math.min(min, step);
			return;
		}
		if (step < dist[x][y][gem]) {
			dist[x][y][gem] = step;
		} else {
			return;
		}
		for (int k = 0; k < 4; k++) {
			int tx = x + dir[k][0];
			int ty = y + dir[k][1];
			if (!inArea(tx, ty) || vis[tx][ty])
				continue;
			if (step + Math.abs(ex-tx) + Math.abs(ey-ty) >= min)
			    continue;
			int t = gem;
			if (grid[tx][ty] == '#') {
			    t++;
                if (t > T) return;
			}
			vis[tx][ty] = true;
			dfs(tx, ty, step+1, t);
			vis[tx][ty] = false;
		}
	}
    public static void main(String[] args) throws IOException {  
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
		R = sc.nextInt();
		C = sc.nextInt();
		T = sc.nextInt();
		grid = new char[R][C];
		vis = new boolean[R][C];
		dist = new int[R][C][T+1];
		int sx = 0, sy= 0;
		
		for (int i = 0; i < R; i++) {
			String s = sc.next();
			for (int j = 0; j < C; j++) {
				grid[i][j] = s.charAt(j);
				if (grid[i][j] == '@') {
					sx = i; sy = j;
				} else if (grid[i][j] == '+') {
					ex = i; ey = j;
				}
				for (int k = 0; k <= T; k++) {
					dist[i][j][k] = INF;
				}
			}
		}
		vis[sx][sy] = true;
		dfs(sx, sy, 0, 0);
		System.out.println(min == INF ? -1 : min);
    }
}

需要注意的问题:一定要注意语句的后效性,有些判断是不能放在另一些判断后面的…

private static void dfs(int x, int y, int step, int gem) {
    if (gem > T)
		return;
	if (step < dist[x][y][gem]) {
		dist[x][y][gem] = step;
	} else {
		return;
	}
       if (step + Math.abs(ex-x) + Math.abs(ey-y) >= min)
           return;
	for (int k = 0; k < 4; k++) {
		int tx = x + dir[k][0];
		int ty = y + dir[k][1];
		if (!inArea(tx, ty) || vis[tx][ty])
			continue;
		vis[tx][ty] = true;
		if (grid[tx][ty] == '#') dfs(tx, ty, step+1, gem+1);
		if (grid[tx][ty] == '*') dfs(tx, ty, step+1, gem);
		if (grid[tx][ty] == '+') min = Math.min(min, step+1);
		vis[tx][ty] = false;
	}
}

你可能感兴趣的:(●,搜索)