题目描述:
考虑一个3*3方格游戏:
游戏的目标是通过移动,让相同颜色的块形成一个连通块(相邻是指两个块有边相邻,角相邻不算)。
移动规则如下:选择一行(列),向左右(上下)移动一格,方格从一边划出,则从对应的另外一边划入,像履带一样。
如选择第一行向右边移动,最右边的那格会移动到最左边。
游戏中还有一些方格被固定住,这些方格没办法移动(如下图的第三行第二列)。
假设现在告诉你初始状态,请问你最少需要几步才能达到目标?
(Time Limit:2000/1000MS(Java/Others) Memory Limit:65535/32768K(Java/Others))
Input
第一行一个整数 T代表接下去有 T组数据;
每组数据由 3*3的模块组成,每个模块表示的小正方形是由上下左右四个小三角形组成;
每个模块有 5个字符,前四个字符分别表示组成正方形的上下左右四个小三角形的颜色,第
五个字符表示该格子能否移动(0表示能移动,1表示不能移动).
[T echnicalSpecification]
0<T<100
代表颜色的字符一定是RGBO的其中一个;
代表能否移动移动的字符一定是 0或者 1;
Output
首先输出 case数,接着输出最小的移动步数使得游戏达到目标状态(见 sample)。
数据保证有解.
SampleInput
2
GGGG0 GGGG0 GGGG0
OGOO0 GGGG0 OGOO0
OOOO0 OGGG1 OOOO0
RRRR0 OOOO0 OOOO0
OOOO0 OOOO0 OOOO0
OOOO0 OOOO0 RRRR0
SampleOutput
Case #1: 5
Case #2: 2
Java源码:
import java.util.Scanner;
import java.util.Set;
import java.util.HashSet;
import java.util.Deque;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.HashMap;
/**
* 2013/03/29 腾讯马拉松复试第2题。
* 问题的解决通过对移动步长的递增穷举来完成。移动路径的查找则是利用了包含状态检测的DFS算法来实现。
* @author Christopher
*
*/
public class Main {
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int dataGroup = in.nextInt();//数据组数
Square[] squareGroup = new Square[dataGroup];//存储每一组数据
//读入数据并初始化
for(int i = 0; i < dataGroup; i++){
squareGroup[i] = new Square();
}
//计算并输出结果
for(int i = 1; i <= dataGroup; i++){
System.out.println("Case #" + i + ": " + squareGroup[i-1].moveToGoal());
}
}
/**
* 该类是对题目中由9个小正方形组成的大正方形的抽象表示。
* @author Christopher
*
*/
static class Square{
private String[][] square = new String[3][3];
private String[][] squareCopy = new String[3][3];//square的副本
private int[][] squareState = new int[3][3];/*记录原来的小正方形序号(0-9)现在在矩阵中的位置,
以此来表示Square的状态*/
private Set<Character> colorSet = new HashSet<Character>();//记录该Square含有的颜色
private Set<Integer> nodeSet = new HashSet<Integer>();/*记录每一种颜色的某一个小三角形的序号,
第(i,j)号小正方形的四个小三角形序号依次为4*(3*i+j)--4*(3*i+j)+3*/
private Map<String, Integer> stateMap = new HashMap<String, Integer>();
/* 记录每一个已经到达过的状态,以便广度遍历时使用。每一个状态由当前Square状态和到达当前状态所需的
* 最小步数组成。*/
private String[] cmd = {"R0", "R1", "R2", "L0", "L1", "L2",
"U0", "U1", "U2", "D0", "D1", "D2"};
private boolean[] canDo = {true, true, true, true, true, true,
true, true, true, true, true, true};//记录相应的命令是否能够执行
private int step = 0;
private int len = 0;
public Square(){
Scanner in = new Scanner(System.in);
for(int i = 0; i < square.length; i++){
for(int j = 0; j < square[i].length; j++){
square[i][j] = in.next();
squareCopy[i][j] = square[i][j];
squareState[i][j] = 3 * i + j;
if(colorSet.size() < 4){
for(int k = 0; k < 4; k++){//记录颜色的种类
if(colorSet.add(square[i][j].charAt(k)))
nodeSet.add(Integer.valueOf(4*(3*i+j)+k));
}
}
}
}
}
/**
* 移动到目标状态,返回最小移动的步数。
* @return
*/
public int moveToGoal(){
if(isConnected()) return 0;
//计算canDo
for(int i = 0; i < square.length; i++){
for(int j = 0; j < square[i].length; j++){
if(square[i][j].charAt(4) == '1'){
for(int k = 0; k < cmd.length; k++){
if(cmd[k].equals("R" + i) || cmd[k].equals("L" + i) ||
cmd[k].equals("U" + j) || cmd[k].equals("D" + j)){
canDo[k] = false;
}
}
}
}
}
//穷举法移动square
for(len = 1; len < Integer.MAX_VALUE; len++){//对步数的长度进行穷举
int result = recursiveMove();
if(result != -1){
return result;
}
else{
reset();
continue;
}
}
return -1;
}
/**
* 深度优先递归穷举,穷举的层数由len确定。
* @return - 找到结果返回当前的步数(也就是当前的层数),否则返回-1。
*/
private int recursiveMove(){
for(int i = 0; i < cmd.length; i++){
if(canDo[i]){
move(cmd[i]);
step++;
String state = currentState();
Integer value = stateMap.get(state);//获取与state对应的键值
if(value != null){//确实已经包含该状态
if(value > step){//需要更新对应的值
stateMap.put(state, value);
}
else{//不需要更新,说明当前路径应舍弃
backTrack(cmd[i]);
step--;
continue;
}
}
else{//当前状态还没有遇到过
stateMap.put(state, value);
}
if(step == len){
if(isConnected()){
// System.out.println("cmd = " + cmd[i]); //这句和下一个输出是为了倒序打印移动路径
return step;
}
else{
backTrack(cmd[i]);
step--;
continue;
}
}
int res = recursiveMove();
if(res != -1){
// System.out.println("cmd = " + cmd[i]); //这句和上一个输出是为了倒序打印移动路径
return res;
}
else{
backTrack(cmd[i]);
step--;
continue;
}
}
else continue;
}
return -1;
}
/**
* 判断该Square是否连通。
* @return - 连通返回true,否则返回false。
*/
private boolean isConnected(){
int nodeSum = 0;
//广度优先遍历计算每一种颜色所在的区域的结点个数之和
Deque<Integer> dq = new ArrayDeque<Integer>(36);//3 * 3 * 4
Set<Integer> nSet = new HashSet<Integer>(36);
for(Integer node : nodeSet){
dq.add(node);
while(!dq.isEmpty()){
Integer n = dq.pop();
nSet.add(n);
//计算对应小正方形的i,j和在小正方形中的序号k
int i, j, k;
k = n % 4;
j = (n - k) / 4 % 3;
i = ((n - k) / 4 - j) / 3;
if(k == 0){
if(i > 0 && square[i-1][j].charAt(1) == square[i][j].charAt(k)
&& !nSet.contains(n-11)){
dq.add(n-11);
}
if(square[i][j].charAt(2) == square[i][j].charAt(k)
&& !nSet.contains(n+2))
dq.add(n+2);
if(square[i][j].charAt(3) == square[i][j].charAt(k)
&& !nSet.contains(n+3))
dq.add(n+3);
}
else if(k == 1){
if(i < 2 && square[i+1][j].charAt(0) == square[i][j].charAt(k)
&& !nSet.contains(n+11)){
dq.add(n+11);
}
if(square[i][j].charAt(2) == square[i][j].charAt(k)
&& !nSet.contains(n+1))
dq.add(n+1);
if(square[i][j].charAt(3) == square[i][j].charAt(k)
&& !nSet.contains(n+2))
dq.add(n+2);
}
else if(k == 2){
if(j > 0 && square[i][j-1].charAt(3) == square[i][j].charAt(k)
&& !nSet.contains(n-3)){
dq.add(n-3);
}
if(square[i][j].charAt(0) == square[i][j].charAt(k)
&& !nSet.contains(n-2))
dq.add(n-2);
if(square[i][j].charAt(1) == square[i][j].charAt(k)
&& !nSet.contains(n-1))
dq.add(n-1);
}
else{// k == 3
if(j < 2 && square[i][j+1].charAt(2) == square[i][j].charAt(k)
&& !nSet.contains(n+3)){
dq.add(n+3);
}
if(square[i][j].charAt(0) == square[i][j].charAt(k)
&& !nSet.contains(n-3))
dq.add(n-3);
if(square[i][j].charAt(1) == square[i][j].charAt(k)
&& !nSet.contains(n-2))
dq.add(n-2);
}
}//while
nodeSum += nSet.size();
nSet.clear();
}
return nodeSum == 36;
}
/**
* 按照命令cmd执行移动。
* cmd是由"方向"和"行(列)数"组成的长度为2的字符串。
* @param cmd
*/
private void move(String cmd){
if(cmd.charAt(0) == 'R' || cmd.charAt(0) == 'L'){//执行移动的方向
int row = Integer.parseInt(String.valueOf(cmd.charAt(1)));//执行移动的行(列)号
String temp = square[row][0];
int tempState = squareState[row][0];
if(cmd.charAt(0) == 'R'){
square[row][0] = square[row][2];
square[row][2] = square[row][1];
square[row][1] = temp;
//状态调整
squareState[row][0] = squareState[row][2];
squareState[row][2] = squareState[row][1];
squareState[row][1] = tempState;
}
else{// 'L'
square[row][0] = square[row][1];
square[row][1] = square[row][2];
square[row][2] = temp;
//状态调整
squareState[row][0] = squareState[row][1];
squareState[row][1] = squareState[row][2];
squareState[row][2] = tempState;
}
}
else{//'U' || 'D'
int col = Integer.parseInt(String.valueOf(cmd.charAt(1)));
String temp = square[0][col];
int tempState = squareState[0][col];
if(cmd.charAt(0) == 'U'){
square[0][col] = square[1][col];
square[1][col] = square[2][col];
square[2][col] = temp;
//状态调整
squareState[0][col] = squareState[1][col];
squareState[1][col] = squareState[2][col];
squareState[2][col] = tempState;
}
else{// 'D'
square[0][col] = square[2][col];
square[2][col] = square[1][col];
square[1][col] = temp;
//状态调整
squareState[0][col] = squareState[2][col];
squareState[2][col] = squareState[1][col];
squareState[1][col] = tempState;
}
}
}
/**
* 按照上一步做cmd移动,移动回上一步。
* @param cmd
*/
private void backTrack(String cmd){
if(cmd.charAt(0) == 'R') move("L" + cmd.charAt(1));
else if(cmd.charAt(0) == 'L') move("R" + cmd.charAt(1));
else if(cmd.charAt(0) == 'U') move("D" + cmd.charAt(1));
else move("U" + cmd.charAt(1));
}
/**
* 该Square复原。
*/
private void reset(){
stateMap.clear();
step = 0;
for(int i = 0; i < square.length; i++){
for(int j = 0; j < square[i].length; j++){
square[i][j] = squareCopy[i][j];
squareState[i][j] = 3 * i + j;
}
}
}
/**
* 返回当前Square的状态,这个状态用小正方形的序号的当前序列表示。
* @return
*/
private String currentState(){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < squareState.length; i++){
for(int j = 0; j < squareState[i].length; j++){
sb.append(squareState[i][j]).append("|");
}
}
return sb.toString();
}
}
}