【ACWing】181. 回转游戏

题目地址:

https://www.acwing.com/problem/content/183/

如下图所示,有一个#形的棋盘,上面有 1 , 2 , 3 1,2,3 1,2,3三种数字各 8 8 8个。给定 8 8 8种操作,分别为图中的 A ∼ H A∼H AH。这些操作会按照图中字母和箭头所指明的方向,把一条长为 7 7 7的序列循环移动 1 1 1个单位。例如下图最左边的#形棋盘执行操作 A A A后,会变为下图中间的#形棋盘,再执行操作 C C C后会变成下图最右边的#形棋盘。给定一个初始状态,请使用最少的操作次数,使 # 形棋盘最中间的 8 8 8个格子里的数字相同。在这里插入图片描述输入格式:
输入包含多组测试用例。每个测试用例占一行,包含 24 24 24个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。当输入只包含一个 0 0 0的行时,表示输入终止。

输出格式:
每个测试用例输出占两行。第一行包含所有移动步骤,每步移动用大写字母 A ∼ H A∼H AH中的一个表示,字母之间没有空格,如果不需要移动则输出No moves needed。第二行包含一个整数,表示移动完成后,中间 8 8 8个格子里的数字。如果有多种方案,则输出字典序最小的解决方案。

为避免搜索的过深,可以用迭代加深来做。这里有一个比较容易想到的启发函数,对于当前状态 s s s,启发函数 h ( s ) h(s) h(s)可以取 8 8 8减去这个状态的中间 8 8 8个数出现最多的数的出现次数,原因是每次操作最多改变中间 8 8 8数的其中一个数(带启发函数的迭代加深算法又叫做IDA*)。为了方便,我们把每个操作 A ∼ H A\sim H AH编号为 0 ∼ 6 0\sim 6 06 7 7 7个整数,然后开个数组 o p op op o p [ i ] op[i] op[i]代表第 i i i种操作对应的字母,在图中对应的哪 7 7 7个数,并且使得这个操作的结果,恰好是使得这 7 7 7个数循环向左移动一位。此外还有一个优化是,当执行 A A A操作之后,不要立即在下一层递归里枚举 F F F操作,因为这两个操作互逆,是无用功。所以在DFS的时候可以将上次执行的是什么操作作为参数传进去。代码如下:

#include <iostream>
using namespace std;

const int N = 24;

int op[8][7] = {
    {0, 2, 6, 11, 15, 20, 22},
    {1, 3, 8, 12, 17, 21, 23},
    {10, 9, 8, 7, 6, 5, 4},
    {19, 18, 17, 16, 15, 14, 13},
    {23, 21, 17, 12, 8, 3, 1},
    {22, 20, 15, 11, 6, 2, 0},
    {13, 14, 15, 16, 17, 18, 19},
    {4, 5, 6, 7, 8, 9, 10}
};

// oppo[i]表示操作i对应的逆操作
int oppo[8] = {5, 4, 7, 6, 1, 0, 3, 2};
// center表示中间8个数在输入里对应的下标
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};

// q存的是输入,也就是当前状态按输入顺序的所有数
int q[N];
// path存操作序列
int path[100];

// 启发函数
int f() {
	// 统计一下中间8个数每个数出现了多少次
    int cnt[4] = {0};
    for (int i = 0; i < 8; i++) cnt[q[center[i]]]++;
	
	// 找到出现次数最多的那个数的出现次数
    int s = 0;
    for (int i = 1; i <= 3; i++) s = max(cnt[i], s);

    return 8 - s;
}

void operate(int x) {
    int t = q[op[x][0]];
    for (int i = 0; i < 6; i++) q[op[x][i]] = q[op[x][i + 1]];
    q[op[x][6]] = t;
}

bool dfs(int depth, int max_depth, int last) {
	int h = f();
    if (depth + h > max_depth) return false;
    // 如果启发函数等于0,那对应的是中间8个数都相等,即到达了终点了,则返回true
    if (!h) return true;

    for (int i = 0; i < 8; i++) {
        if (last == oppo[i]) continue;
        operate(i);
        path[depth] = i;
        if (dfs(depth + 1, max_depth, i)) return true;
        operate(oppo[i]);
    }

    return false;
}

int main() {
	// 如果读入的第一个数是0就直接退出循环
    while (cin >> q[0], q[0]) {
        for (int i = 1; i < N; i++) cin >> q[i];

        int max_depth = 0;
        // dfs的第三个参数是上次操作的编号,-1表示不存在上次操作
        // 之所以从第0层开始搜,是因为有可能一开始就已经是终点了
        while (!dfs(0, max_depth, -1)) max_depth++;
		
		// 如果一开始就是终点,则不需要任何移动
        if (!max_depth) cout << "No moves needed" << endl;
        else {
        	// 否则输出路径
            for (int i = 0; i < max_depth; i++) printf("%c", path[i] + 'A');
            cout << endl;
        }

        printf("%d\n", q[center[0]]);
    }

    return 0;
}

时间复杂度指数级,空间复杂度与解有多深有关。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值