【题目链接】

POJ 2286 The Rotaion Game COGS 1073 轮回游戏 (这名字...不错的翻译>_<)

【题目描述】

有一个 # 形棋盘,上面有24个格子(如下图)。这些格子上面有1,2,3三种数字,且每种数字有8个。一开始这些格子上的数字是随机分布的。你的任务是移动这些格子使得中间8个格子的数字相同。有8种移动方式,分别标记为A至H,可以理解为拉动4条链,如图的变换为‘AC’。问至少需要多少次拉动,才能从初始状态到达目标状态?

IMG1

若有多组解,则输出字典序最小的那个。保证数据有解。


【解题思路】

典型的状态空间搜索问题,但是如果像八数码一样直接采取BFS可能~~(一定)~~会超时。 我们考虑IDA*算法,由于每次操作至多能使一个目标数字到达中间位置(因为每次只能移动一格),所以我们令乐观函数 $h(n) = min(diff(1),diff(2),diff(3))$ ,其中 $diff(i)$ 表示棋盘中间格子上数字不是 $i$ 的格子个数。这样当$d + h(n) > limit$时剪枝,本题的主算法就这样定了。

具体实现上,为格子连续编号。将每种操作影响到的格子写出来,同时也要储存每种操作的逆操作,以便恢复现场。


【AC代码】

#include <cstdio>
#include <algorithm>

#define MAXN 30
#define MAXSTEP 1000
// 定为1000是没有什么道理的

// 每种操作涉及的格子
int line[8][7]={
  {  0,  2,  6, 11, 15, 20, 22 }, // A
  {  1,  3,  8, 12, 17, 21, 23 }, // B
  { 10,  9,  8,  7,  6,  5,  4 }, // C
  { 19, 18, 17, 16, 15, 14, 13 }, // D
  // E~H 操作借助其逆操作等会儿处理出来
};

const int rev[] = { 5, 4, 7, 6, 1, 0, 3, 2 }; // 每种操作的逆操作

const int center[8] = {6, 7, 8, 11, 12, 15, 16, 17}; // 格子中间位置的编号

int map[MAXN];
char ans[MAXSTEP];

int limit;
// 判断是否达到终态
inline bool isFinal(){
    int a = map[center[0]];
    for(int i = 0; i < 8; i++){
        if(map[center[i]] != a) return false;
    }
    return true;
}

inline int h(){
    int a = 0, b = 0, c = 0;
    for(int i = 0; i < 8; i++){
        if(map[ center[i] ] != 1) a++;
        if(map[ center[i] ] != 2) b++;
        if(map[ center[i] ] != 3) c++; // c++ (滑稽)
    }
    return std::min(std::min(a, b), c);
}
// 移动格子的操作:
inline void move(int i){
    int *k = line[i];
    int a = map[ k[0] ];
    for(int j = 0; j < 6; j++) map[ k[j] ] = map[ k[j + 1] ];
    map[ k[6] ] = a;
}

bool dfs(int d){
    if(isFinal()){
        ans[d] = '\0';
        printf("%s\n", ans);
        return true;
    }

    if(d + h() > limit) return false;

    for(int i = 0; i < 8; i++){
        ans[d] = 'A' + i;
        move(i);
        if(dfs(d + 1)) return true;
        move(rev[i]); // 完成后恢复现场
    }

    return false;
}

inline void init(){
    for(int i = 4; i < 8; i++){
        for(int j = 0; j < 7; j++){
            line[i][j] = line[ rev[i] ][6 - j]; // 处理出E~H操作
        }
    }
}

inline bool solve(){
    if(isFinal()) return false;
    else for(limit = 1; ; limit++){
        if(dfs(0)) return true;
    }
}

int main(){
    freopen("rotationa.in", "r", stdin), freopen("rotationa.out", "w", stdout);
    init();
    while(scanf("%d", map) == 1&& map[0]){
        for(int i = 1; i < 24; i++) scanf("%d", map + i);
        int ans = solve();
        if(!ans) puts("No moves needed");
        printf("%d\n", map[ center[0] ]);
    }
    fclose(stdin), fclose(stdout);
    return 0;
}

就这样啦