【题解】P2324 [SCOI2005] 骑士精神 [IDA*]
P2324 [SCOI2005] 骑士精神 - 洛谷 (luogu.com.cn)
原来这就叫 IDA* 啊,我之前自己瞎捣鼓也有搞出过这种剪枝。
第一眼好想状态压缩 + bfs,一算状态数:。
只能从题目限制 15 步下手,考虑深搜 + 剪枝。
步骤:
(0)骑士移动太麻烦,考虑移动空白格
(1)从小到大枚举 0 到 15 步作为最大步 maxdep,用 dfs 验证
(2)dfs 里移动一次空白格为增加一步,如果当前步数比 maxdep 大就退出
(3)当前 maxdep 可以移动成目标棋盘就输出
这样做状态数和 bfs 的差不了多少,但 dfs 有一个很好的点:剪枝够给力。
我们给每个状态都计算一个 “完美估价步数” mn(当前状态与目标状态的不相同格子数),
这代表这个状态距离目标状态可能的最少步数。
如果当前步数 + 最少步数 mn > maxdep,那么剪枝。
不过还有个细节:
因为移动的是空白格,只有在最后一步才有可能一步消除 2 的差异,其他时候一步最多只能消除 1 的差异,所以非 0 差异数 - 1。
这样就能严格保证估价 >= 实际花费。
还有个剪枝,在搜索到正解后 flag = 1,其他分支直接退出。
还有往回移动,这也可以剪掉。
时间复杂度很难算,我们可以从最坏角度考虑:
根据模拟情况,粗略估计每轮剪掉 5 个支(实际上 15 轮平均每轮剪 6 ~ 7 个)无解数据的状态数:3 ^ 15 = 14,348,907还有两个剪枝没算进去,实际上小得多最坏情况(10 组无解数据):操作次数量级最大在 1e8 左右
实际上,这种提前计算未来代价的剪枝,就叫做 IDA* 算法。
常用于 dfs 剪枝中,常见形式为:
记录到达当前结点 x
的实际成本 g(x)
,并利用它到终点的最小成本估计 h(x)
进行剪枝。
设 f(x) = g(x) + h(x),如果 f(x) 超过阈值 C
,则停止对该分支的搜索。
代码:
#include<bits/stdc++.h>
using namespace std;char s[7][7];
int mat[7][7], a[7][7];
int goal[7][7] = {{0, 0, 0, 0, 0, 0},{0, 1, 1, 1, 1, 1},{0, 0, 1, 1, 1, 1},{0, 0, 0, 2, 1, 1},{0, 0, 0, 0, 0, 1},{0, 0, 0, 0, 0, 0}
};
int mxdep;
int dx[8] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[8] = {-1, 1, 2, 2, 1, -1, -2, -2};
int anti[9] = {4, 5, 6, 7, 0, 1, 2, 3, -1};
bool flag;int get_eva() {int sum = 0;for (int i = 1; i <= 5; i ++) {for (int j = 1; j <= 5; j ++) {if (mat[i][j] != goal[i][j]) {sum ++;}}}return (sum == 0)? 0 : sum - 1;
}void dfs(int x, int y, int dep, int last) {if (flag) {return ;}if (dep == mxdep) {if (get_eva() == 0) {flag = 1;}return ;}for (int i = 0; i <= 7; i ++) if (i != anti[last]) {int xx = x + dx[i], yy = y + dy[i];if (xx >= 1 && xx <= 5 && yy >= 1 && yy <= 5) {swap(mat[x][y], mat[xx][yy]);int mn = get_eva();if (mn == 0) {flag = 1;return ;}if (mn + dep + 1 <= mxdep) {dfs(xx, yy, dep + 1, i);}swap(mat[x][y], mat[xx][yy]);}}
}int main () {ios::sync_with_stdio(false);cin.tie(0);int T; cin >> T;while (T --) {int x = 0, y = 0;for (int i = 1; i <= 5; i ++) {cin >> (s[i] + 1);for (int j = 1; j <= 5; j ++) {if (s[i][j] == '*') {mat[i][j] = 2;x = i; y = j;} else {mat[i][j] = s[i][j] - '0';}a[i][j] = mat[i][j];}}for (int i = 0; i <= 15; i ++) {flag = 0;mxdep = i;memcpy(mat, a, sizeof(a));dfs(x, y, 0, 8);if (flag) {cout << i << "\n";break;}}if (flag == 0) {cout << "-1\n";}}return 0;
}
