Problem:
Problem I: 盗まれた宝石

パル王国の国宝である宝石が盗賊たちに盗まれた。 冒険者のあなたはその噂を聞きつけて盗賊のアジトに向かい、なんとか宝石を取り戻すことが出来た。

ところが、宝石をパル王国の城へ返しに出向いたところ、城の守衛兵から、 「我が王はまだあなたを完全に信用していない。その宝石が本物かどうか怪しいし、そもそも宝石を取り戻したというのがウソで、王の命を狙う盗賊の仲間なのではないか？と疑っておられる。」 といわれた。

あなたは、守衛兵から本当に信頼できる人物かどうか試すための試練を与えられた。試練の内容は、 「城の地下倉庫へ行き指定された場所に宝石を置け。そこに宝石が本物だったときに反応する魔法陣が書かれてある。ただしあなたが怪しい行動を起こさないようにするため、地下倉庫の入り口から指定された場所に向かうまで、守衛兵が言った移動パターンをとってはならない。」 というものだった。

例えば、下図にある地下倉庫の形状、禁止パターンに対し、図にかかれてあるような移動パターンを取ってはならない。移動パターンの一部(赤くハイライトされている)が、禁止パターンの中に含まれるからである。(また、この移動パターンの中の2,3番目の動き「↓↓」も禁止パターンの中に含まれる)

一方、 下図のような移動パターンは、移動パターンのどの部分も禁止パターンの中に含まれないので許される。

入力として、地下倉庫の形状、禁止パターンが与えられる。禁止パターンを取らずに、入り口から魔法陣へ移動するのに最低限必要な移動回数を求めよ。

Input

入力には、地下倉庫の形状と禁止パターンが含まれる。

地下倉庫の形状は、以下のように表される。 まず、二つの整数 N,M が与えられる。それぞれ地下倉庫の行数と列数を意味する。(1 ≤ N,M ≤ 50)

続いて、それぞれM個の文字からなる文字列がN行与えられる。 含まれる文字とその意味は以下の通りである。

文字

意味

S

地下倉庫の入り口。一つの地下倉庫につき必ず一つだけ含まれる。

G

魔法陣。一つの地下倉庫につき必ず一つだけ含まれる。

.

通路。(禁止パターンをとらなければ)通ることが出来る。

#

壁。壁の中を通ることはできない。

次に、禁止パターンが与えられる。禁止パターンは以下のように表される。 まず、一つの整数 P が与えられる。禁止パターンの数を意味する。(0 ≤ P ≤ 10)

続いて、禁止パターンを意味する文字列がP行にわたって与えられる。 禁止パターンに含まれる文字とその意味は以下の通りである。

文字

意味

U

↑の移動。

R

→の移動。

D

↓の移動。

L

←の移動。

禁止パターンの長さは1以上、10以下である。 ある禁止パターンが、別の禁止パターンの部分文字列となっていることもある。また、同一の禁止パターンが含まれることもある。

Output

最低限必要な移動回数を意味する整数を出力せよ。魔法陣へ辿りつけない場合は -1 を出力せよ。

Notes on Test Cases

上記入力形式で複数のデータセットが与えられます。各データセットに対して上記出力形式で出力を行うプログラムを作成して下さい。

n, m が 0 のとき入力の終わりを示します。

Sample Input

7 6
......
.####.
.####.
...S#.
...##.
...##.
.....G
3
LD
DD
LLL
7 8
S#......
.#.####.
.#.#G.#.
.#.##.#.
.#....#.
.######.
........
8
DDDD
DDDU
UUUU
UUUD
RRRR
RRRL
LLLL
LLLR
3 8
########
S......G
########
2
U
D
6 10
..........
.S........
..........
..........
........G.
..........
0
6 7
.......
...#...
...#.S.
...###.
.G.....
.......
2
LL
DD
0 0

Output for Sample Input

13
60
7
10
-1

Solution in JAVA (judge verdict: Accepted)


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;

public class Main{
	
	String INPUT = "./data/judge/201709/A2212.txt";
	
	public static void main(String[] args) throws IOException {
		new Main().run();
	}
	
	static final char[] TAG = {'D','U','L','R'};
	static final int[] R = {1, -1, 0, 0};
	static final int[] C = {0, 0, -1, 1};
	static final int INF = 0x3f3f3f3f;
	
	static int idx = 0;
	
	class State{
		int i;
		int j;
		Node s;
		State(int i, int j, Node s){
			this.i = i;
			this.j = j;
			this.s = s;
		}
		
		@Override
		public int hashCode() {
			return i * 31 + j * 17 + s.id * 11;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (this == obj) return true;
			if (obj == null ||obj.getClass() != getClass()) return false;
			State o = (State) obj;
			return o.i == i && o.j == j && o.s == s;
		}
		
		@Override
		public String toString() {
			return i + " " + j + " " + s;
		}
	}
	
	class Node{
		Map<Character, Node> cs = new TreeMap<Character, Node>();
		Node[] next = new Node[4];
		Node fail;
		boolean accept;
		int id;
		
		Node() {id = idx++;}
		
		@Override
		public String toString() {
			return id + " " + accept;
		}
	}
	
	Node root;
	void AhoCorasick(char[][] p) {
		int n = p.length;
		root = new Node();
		for (int i = 0; i < n; ++i) {
			Node t = root;
			for (char c : p[i]) {
				if (!t.cs.containsKey(c)) t.cs.put(c, new Node());
				t = t.cs.get(c);
			}
			t.accept = true;
		}
		
		// fail
		Queue<Node> que = new LinkedList<Node>();
		que.offer(root);
		while (!que.isEmpty()) {
			Node r = que.poll();
			for (Map.Entry<Character, Node> entry : r.cs.entrySet()) {
				char c = entry.getKey();
				Node v = entry.getValue();
				que.offer(v);
				Node f = r.fail;
				while (f != null && !f.cs.containsKey(c)) {
					f = f.fail;
				}
				if (f == null) {
					v.fail = root;
				}
				else {
					v.fail = f.cs.get(c);
				}
			}
		}
		
		// next
		que.offer(root);
		while (!que.isEmpty()) {
			Node t = que.poll();
			outer : for (char[] cs : p) {
				Node r = t;
				for (char c : cs) {
					if (r.cs.containsKey(c)) {
						r = r.cs.get(c);
					}
					else continue outer;
				}
				r.accept = true;
			}
			for (int i = 0; i < 4; ++i) {
				char c = TAG[i];
				Node r = t;
				while (r != null && !r.cs.containsKey(c)) {
					r = r.fail;
				}
				if (r == null) {
					t.next[i] = root;
				}
				else {
					t.next[i] = r.cs.get(c);
				}
			}
			for (Node r : t.cs.values()) {
				que.offer(r);
			}
		}
	}
	
	int startR;
	int startC;
	int endR, endC;
	
	int solve(char[][] f) {
		int N = f.length;
		int M = f[0].length;
		int n = ni();
		char[][] p = new char[n][];
		for (int i = 0; i < n; ++i) {
			p[i] = ns().toCharArray();
		}
		
		AhoCorasick(p);
		
		State state = new State(startR, startC, root);
		Queue<State> queue = new LinkedList<>();
		Set<State> vis = new HashSet<>();
		queue.offer(state);
		vis.add(state);
		
		int turn = 0;
		int ans  = INF;
		boolean find = false;
		outer: while (!queue.isEmpty() && !find) {
			int size = queue.size();
			for (int i = 0; i < size; ++i) {
				State now = queue.poll();
				if (now.i == endR && now.j == endC) {
					ans = turn;
					find = true;
					break outer;
				}
				for (int j = 0; j < 4; ++j) {
					int nx = now.i + R[j];
					int ny = now.j + C[j];
					if (nx < 0 || nx >= N || ny < 0 || ny >= M) continue;
					if (f[nx][ny] == '#') continue;
					Node next = now.s.next[j];
					if (next.accept) continue;
					State ns = new State(nx, ny, next);
					if (vis.contains(ns)) continue;
					queue.offer(ns);
					vis.add(ns);
				}
			}
			turn++;
		}
		return ans == INF ? -1 : ans;
	}
	
	void read() {
		while (true) {
			int N = ni();
			if (N == 0) break;
			int M = ni();
			char[][] f = new char[N][M];
			for (int i = 0; i < N; ++i) {
				char[] cs = ns().toCharArray();
				for (int j = 0; j < M; ++j) {
					f[i][j] = cs[j];
					if (cs[j] == 'S') {
						startR = i;
						startC = j;
					}
					if (cs[j] == 'G') {
						endR = i;
						endC = j;
					}
				}
			}
			idx = 0;
			out.println(solve(f));
		}
	}

	FastScanner in;
	PrintWriter out;
	
	void run() throws IOException {
		boolean oj;
		try {
			oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
		} catch (Exception e) {
			oj = System.getProperty("ONLINE_JUDGE") != null;
		}
		
		InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
		in = new FastScanner(is);
		out = new PrintWriter(System.out);
		long s = System.currentTimeMillis();
		read();
		out.flush();
		if (!oj){
			System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
		}
	}
	
	public boolean more(){
		return in.hasNext();
	}
	
	public int ni(){
		return in.nextInt();
	}
	
	public long nl(){
		return in.nextLong();
	}
	
	public double nd(){
		return in.nextDouble();
	}
	
	public String ns(){
		return in.nextString();
	}
	
	public char nc(){
		return in.nextChar();
	}
	
	class FastScanner {
		BufferedReader br;
		StringTokenizer st;
		boolean hasNext;

		public FastScanner(InputStream is) throws IOException {
			br = new BufferedReader(new InputStreamReader(is));
			hasNext = true;
		}

		public String nextToken() {
			while (st == null || !st.hasMoreTokens()) {
				try {
					st = new StringTokenizer(br.readLine());
				} catch (Exception e) {
					hasNext = false;
					return "##";
				}
			}
			return st.nextToken();
		}
		
		String next = null;
		public boolean hasNext(){
			next = nextToken();
			return hasNext;
		}

		public int nextInt() {
			if (next == null){
				hasNext();
			}
			String more = next;
			next = null;
			return Integer.parseInt(more);
		}

		public long nextLong() {
			if (next == null){
				hasNext();
			}
			String more = next;
			next = null;
			return Long.parseLong(more);
		}

		public double nextDouble() {
			if (next == null){
				hasNext();
			}
			String more = next;
			next = null;
			return Double.parseDouble(more);
		}
		
		public String nextString(){
			if (next == null){
				hasNext();
			}
			String more = next;
			next = null;
			return more;
		}
		
		public char nextChar(){
			if (next == null){
				hasNext();
			}
			String more = next;
			next = null;
			return more.charAt(0);
		}
	}
	
	static class ArrayUtils {

		public static void fill(int[][] f, int value) {
			for (int i = 0; i < f.length; ++i) {
				Arrays.fill(f[i], value);
			}
		}
		
		public static void fill(int[][][] f, int value) {
			for (int i = 0; i < f.length; ++i) {
				fill(f[i], value);
			}
		}
		
		public static void fill(int[][][][] f, int value) {
			for (int i = 0; i < f.length; ++i) {
				fill(f[i], value);
			}
		}
	}
}