AtCoder Beginner Contest 007 C - 幅優先探索

昨日は、解法に感心した問題をブログに書きましたが、
今日は、解説を見ても解けなかった問題を記しておきます。

解くことのできなかった問題は、
カテゴリー「負債」につっこんで、残しておくことにしました。
あとで見返して解くことができた時に、晴れてこの記事は「負債」から取り除かれます。

そして、未来の自分に宛ててるので、少し言葉遣いが雑になります。

AtCoder Beginner Contest 007 C - 幅優先探索


C - 幅優先探索

問題文があまりに長いので、引用しないからリンク先参照。

考え方

AtCoder Beginner Contest 007 解説

解説にもある通り、問題文で紹介されていた「幅優先探索」の手法をそのまま実装。

最大50マス×50マスになる迷路を、char型の二次配列[y座標][x座標]で初期化し、
それぞれの要素には、壁を表す'#'と、空きを表す'.'のどちらかが入っている。

上下左右のマスが空きマスかどうかを判定するために、
「y座標、x座標、手数」のメンバーを持つCellクラスを用意し、
Cellクラス型の一次配列でキューを作成。

キューには最初に、スタート地点の座標を持つ1つだけが格納されていて、
ゴールを見つけるまで以下のループを繰り返す。

1. popメソッドでキューの先頭のCellクラスを取り出す。
2. 取り出したCellクラスの(y , x)座標を使用して、上下左右のマスが空きマスかどうかを判定。
3. 空きマスだった場合は、そのマスの座標を持つCellクラスをキューの末尾に追加。
4. 次回以降の検索で、再びこのマスに逆戻りしないよう、現在のマスを'#'(壁マス)に変更。

提出したソースコード

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
	static int head = 0;	// キューの先頭
	static int tail = 0;	// キューの末尾
	static Cell[] queue = new Cell[255];

	public static void main(String[] args) {
		try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));) {
			// 行と列の値の受け取り
			String[] RC = br.readLine().split(" ");
			int R = Integer.parseInt(RC[0]);
			int C = Integer.parseInt(RC[1]);

			// スタートマスの座標の受け取り
			String[] sysx = br.readLine().split(" ");
			int sy = Integer.parseInt(sysx[0]) - 1; // 配列は0オリジンなのでそれぞれ-1
			int sx = Integer.parseInt(sysx[1]) - 1;

			// ゴールマスの座標の受け取り
			String[] gygx = br.readLine().split(" ");
			int gy = Integer.parseInt(gygx[0]) - 1;
			int gx = Integer.parseInt(gygx[1]) - 1;

			// 迷路の内容を二次配列で保管
			char[][] labyrinth = new char[R][C];
			for (int i = 0; i < R; i++) {
				String cell = br.readLine();
				for (int j = 0; j < C; j++) {
					labyrinth[i][j] = cell.charAt(j);
				}
			}

			// 上下左右のマスが、空きマスか壁マスかを判定
			push(sy, sx, 0);
			boolean hasGoaled = false;
			int ans = -1;
			while (!hasGoaled) {
				Cell cur = pop();
				// ゴールマスを見つけられたら抜ける
				if (cur.y == gy && cur.x == gx) {
					ans = cur.cnt;
					hasGoaled = true;
				}
				// 上
				if (labyrinth[cur.y - 1][cur.x] == '.') {
					push(cur.y - 1, cur.x, cur.cnt + 1);
				}
				// 下
				if (labyrinth[cur.y + 1][cur.x] == '.') {
					push(cur.y + 1, cur.x, cur.cnt + 1);
				}
				// 左
				if (labyrinth[cur.y][cur.x - 1] == '.') {
					push(cur.y, cur.x - 1, cur.cnt + 1);
				}
				// 右
				if (labyrinth[cur.y][cur.x + 1] == '.') {
					push(cur.y, cur.x + 1, cur.cnt + 1);
				}
				// 現在のマスは、逆戻りしないように壁マスにしてしまう
				labyrinth[cur.y][cur.x] = '#';

			}
			System.out.println(ans);

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	static void push(int y, int x, int cnt) {
		if ((tail + 1) % queue.length != head) {
			queue[tail] = new Cell(y, x, cnt);
			tail = (tail + 1) % queue.length;
		} else {
			System.err.println("キューが満タンです");
		}
	}

	static Cell pop() {
		Cell ret;
		if (head != tail) {
			ret = queue[head];
			head = (head + 1) % queue.length;
		} else {
			System.err.println("キューが空っぽです");
			ret = null;
		}
		return ret;
	}

}

class Cell {
	int y;	// y座標
	int x;	// x座標
	int cnt;	// 手数

	public Cell(int y, int x, int cnt) {
		this.y = y;
		this.x = x;
		this.cnt = cnt;
	}
}


実行結果

入力例1,2のような小さな迷路は「AC」になるが、
入力例3のように広い迷路の場合、すぐにキューが満杯になってしまい「WA」になる

なお、キューの大きさをヤケクソで「10^6」くらいにしても満杯になるし、その場合は「TLE」に変わる。
(キューはリング上にしているので、本当に中身が満タンになっている)


要するに、とんでもない量のオーダーとなってしまう……


おそらく、同じマスを色んなルートから通ることができるので、
単純に50 * 50 の容量じゃキューは足りないのだと思う。

とはいえ、実行するごとに、
空きマスは壁マスに変更されて減っていくため、サイズを10^6に取っても満杯になるのは、
そもそも引数とか変数の設定にミスがあるのかも?