Problem:
Problem E: Mirror Cave

双子の冒険者 Rin と Len は鏡の洞窟でお宝を探している．この洞窟には鏡の間という対になった 2 つの部屋があり，その部屋の扉の向こうには高価な宝が眠っているという．

便宜上，2 つの部屋はそれぞれ W × H のセルが格子状に並んだものとみなす．部屋の外側は壁で囲まれている．また，部屋の内側にもところどころ壁が設置されており，壁の配置されているセルには進入することができない．宝の部屋への扉を開くためには，2 人が左右対称の動きをして，同時に特定のセルに到達しなければならない．しかも，片方だけが先に到達した場合は，扉はロックされて開かなくなってしまう．

左右対称の動きとは，北と北，西と東，東と西，南と南にそれぞれ同時に動くことを意味する．ただし，片方が動こうとしている先に壁があり，もう一方の先に壁が無いといった状況下でも左右対称の動きであると認められる．この場合は壁がある方はその場にとどまり，壁が無い方は隣のセルに動くことになる．

入力として，双子の初期位置，目的地，障害物の配置が与えられる．あなたの仕事は，この双子が扉を開けることが可能であるか判定するプログラムを書くことである．

Input

入力は複数のデータセットからなる．各データセットは次の形式で与えられる．

W H

RoomL1 RoomR1

RoomL2 RoomR2

...

RoomLH RoomRH

最初の行では 2 つの正の整数 W, H (1 ≤ W, H ≤ 50) が 1 つの空白で区切られて与えられる．その後，H 行に渡って 2 つの部屋の情報が与えられる．RoomLi，RoomRi はそれぞれ左側，右側の部屋の i 番目の行に対応する．両者とも長さ W の文字列で，j 番目の文字が部屋の j 番目の列に対応する．それぞれの文字は以下のいずれかである．

. : 自由に移動できるセル

# : 壁のあるセル（進入できない）

% : 目的地

R : Rin の初期位置

L : Len の初期位置

各部屋とも 1 行目が部屋の北端，1 列目が部屋の西端に対応する．また，各部屋は必ずちょうど 1 つの目的地があり，左側の部屋には Len，右側の部屋には Rin の初期位置がちょうど 1 つ存在する．

入力の終わりは，空白で区切られた 2 つの 0 を含む行で示される．

Output

各データセットについて，双子が扉を開くことができるなら Yes，できなければ No をそれぞれ 1 行に出力せよ．

Sample Input

5 5
%#... ...#%
.#.#. .#.#.
.#.#. .#.#.
.#.#. .#.#.
...#L R#...
3 2
.L. .R#
%.. .%.
4 1
L.%. %..R
0 0

Output for the Sample Input

Yes
Yes
No

Solution in JAVA (judge verdict: Accepted)

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;

public class Main {

	static char[][] squaresL;
	static char[][] squaresR;
	static boolean[][][][] isVisited;
	//動き方(左で言うと上下左右)
	static int[][] leftMove = {{0,1},{0,-1},{-1,0},{1,0}};
	static int[][] rightMove = {{0,1},{0,-1},{1,0},{-1,0}};
	static int H;
	static int W;

	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);

		while(true) {

			W = sc.nextInt();
			H = sc.nextInt();

			if((W|H) == 0) {
				break;
			}
			String rawL="";
			String rawR="";

			squaresL = new char[H][W];
			squaresR = new char[H][W];

			for(int h = 0; h < H; h++) {
				rawL=sc.next();
				rawR=sc.next();
				for(int w = 0; w < W; w++) {
					squaresL[h][w] = rawL.charAt(w);
					squaresR[h][w] = rawR.charAt(w);
				}
			}

			int initLh =0;
			int initLw=0;
			int initRh=0;
			int initRw=0;
			for(int h = 0; h < H; h++) {
				for(int w = 0; w < W; w++) {
					if(squaresL[h][w]=='L') {
						initLh = h;
						initLw=w;
					}
					if(squaresR[h][w]=='R') {
						initRh=h;
						initRw=w;
					}
				}
			}

			//Rin Lenの場所セットを一括で管理する
			//左の高さ、横、右の高さ、横
			isVisited = new boolean[H][W][H][W];
			isVisited[initLh][initLw][initRh][initRw] = true;

			Queue<int[]> q = new ArrayDeque<int[]>();

			//左の高さ、横、右の高さ、横
			q.add(new int[]{initLh, initLw, initRh, initRw});

			int[] nowSquares;

			boolean isYes = false;

			while(!q.isEmpty()) {
				nowSquares = (int[]) q.poll();
				//次のステップの状態を見る
				for(int i = 0;i< 4 ; i++) {
					int[] nextSquares = getNext(nowSquares, i);
					int nextLh=nextSquares[0];
					int nextLw=nextSquares[1];
					int nextRh=nextSquares[2];
					int nextRw=nextSquares[3];

					//すでに訪れたことがあるなら無視
					if(isVisited[nextLh][nextLw][nextRh][nextRw] == true) {
						continue;
					}else if(squaresL[nextLh][nextLw]=='%' && squaresR[nextRh][nextRw] == '%'){
						//同時に扉につくならYesを出力
						isYes=true;
						break;

					}else if(squaresL[nextLh][nextLw]=='%' || squaresR[nextRh][nextRw] == '%') {
						//片方のみ先に扉につくなら無視
						continue;
					}else {
						//次のステップに進む場合、フラグを立ててキューに入れる
						isVisited[nextLh][nextLw][nextRh][nextRw] = true;
						q.add(nextSquares);
					}
				}

			}
			if(isYes) {
				System.out.println("Yes");
			}else {
				System.out.println("No");
			}

		}

	}

	static int[] getNext(int[] nowSquares, int idx) {
		int nowLh = nowSquares[0];
		int nowLw = nowSquares[1];
		int nowRh = nowSquares[2];
		int nowRw = nowSquares[3];

		int nextLh;
		int nextLw;
		int nextRh;
		int nextRw;

		if(nowLh + leftMove[idx][1] < 0 || nowLh + leftMove[idx][1] >= H
				|| nowLw + leftMove[idx][0] < 0 || nowLw + leftMove[idx][0] >= W) {
			//左が範囲外に出る場合
			nextLh = nowLh;
			nextLw = nowLw;
		}else {
			//左が範囲内へ動く場合
			char nextL = squaresL[nowLh + leftMove[idx][1]][nowLw + leftMove[idx][0]];

			if(nextL =='#') {
				//行き先が壁
				nextLh = nowLh;
				nextLw = nowLw;
			}else {
				//移動する
				nextLh = nowLh + leftMove[idx][1];
				nextLw = nowLw + leftMove[idx][0];
			}
		}

		if(nowRh + rightMove[idx][1] < 0 || nowRh + rightMove[idx][1] >= H
				|| nowRw + rightMove[idx][0] < 0 || nowRw + rightMove[idx][0] >= W) {
			//右が範囲外に出る場合
			nextRh = nowRh;
			nextRw = nowRw;
		}else {
			//右が範囲内へ動く場合
			char nextR = squaresR[nowRh + rightMove[idx][1]][nowRw + rightMove[idx][0]];

			if(nextR =='#') {
				//行き先が壁
				nextRh = nowRh;
				nextRw = nowRw;
			}else {
				//移動する
				nextRh = nowRh + rightMove[idx][1];
				nextRw = nowRw + rightMove[idx][0];
			}
		}

		return new int[] {nextLh, nextLw, nextRh, nextRw};
	}
}