[SCOI2005]騎士精神
描述
在一個\(5×5\)的棋盤上有\(12\)個白色的騎士和\(12\)個黑色的騎士, 且有一個空位。在任何時候一個騎士都能按照騎
士的走法(它可以走到和它橫坐標相差為\(1\),縱坐標相差為\(2\)或者橫坐標相差為\(2\),縱坐標相差為\(1\)的格子)移動到空
位上。 給定一個初始的棋盤,怎樣才能經過移動變成如下目標棋盤: 為了體現出騎士精神,他們必須以最少的步
數完成任務。
輸入
第一行有一個正整數\(T(T<=10)\),表示一共有\(N\)組數據。接下來有\(T\)個\(5×5\)的矩陣,\(0\)表示白色騎士,\(1\)表示黑色騎 士,\(*\)表示空位。兩組數據之間沒有空行。
輸出
對於每組數據都輸出一行。如果能在\(15\)步以內(包括\(15\)步)到達目標狀態,則輸出步數,否則輸出-\(1\)。
輸入樣例 1:
2 10110 01*11 10111 01001 00000 01011 110*1 01110 01010 00100
輸出樣例1:
7 -1
題解
題意:給你一個初始棋盤,要求用最少的步數移動馬達到如上圖的目標狀態(要求棋盤中的馬只能走“日”)。
咱們先拋開\(IDA^*\),先如何優化爆搜;
這里的馬和象棋里的馬走法相同,但題目中要求讓馬走,但是要是馬的話,搜索分支比較多,所以我們要考慮讓空格走(很顯然吧)。
下面步入正題:
\(IDA^*\)就是帶有迭代加深和估價函數優化的搜索。
可能某些人對以上兩個名詞很陌生,下面一些前置知識可能會帶你透徹一下。
前置知識1:迭代加深
定義:
每次限定一個\(maxdep\)最大深度,使搜索樹的深度不超過\(maxdep\)。
for(R int maxdep=1;maxdep<=題目中給的最大步數;maxdep++){
dfs(0,maxdep);//0為出入函數中當前步數,maxdep為傳入的最大深度。
if(success)break;//如果搜索成功則會在dfs函數中將success賦值為1。
}
使用范圍:
1.在有一定的限制條件時使用(例如本題中“如果能在\(15\)步以內(包括\(15\)步)到達目標狀態,則輸出步數,否則輸出\(-1\)。“)。
2.題目中說輸出所以解中的任何一組解。
為什么能夠降低時間復雜度:
我們可能會在一個沒有解(或解很深的地方無限遞歸然而題目中要求輸出任何的一組解),所以我們限制一個深度,讓它去遍歷更多的分支,去更廣泛地求解,(其實和\(BFS\)有異曲同工之妙)。
前置知識2:估價函數
定義:
\(f(n)=g(n)+h(n)\)
其中\(f(n)\)是節點的估價函數,\(g(n)\)是現在的實際步數,\(h(n)\)是對未來步數的最完美估價(“完美”的意思是可能你現實不可能實現,但你還要拿最優的步數去把\(h(n)\)算出來,可能不太好口胡,可以參考下面的實例)。
應用:
void dfs(int dep,int maxdep){
if(evaluate()+dep>maxdep)return;
//evaluate函數為對未來估價的函數,若未來估價加實際步數>迭代加深的深度則return。
if(!evaluate){
success=1;
printf("%d\n",dep);
return;
}
......
}
前置知識3:\(A^*\)和\(IDA^*\)的區別
\(A^*\)是用於對\(BFS\)的優化;
\(IDA^*\)是對結合迭代加深的\(DFS\) 的優化。
本質上只是在\(BFS\)和\(DFS\)上加上了一個估價函數。
何時使用因題而定:
\(A^*\)([SCOI2007]k短路);\(IDA^*\)([SCOI2005]騎士精神和UVA11212 Editing a Book 就是上面的兩道題)。
前置知識畢!!!
現在就是要想一個比較好的估價函數(若估價函數不好的話,優化效率就並不高,例如若估價函數一直為0,那就是爆搜)。
我們可以想一下,每次空白格子和黑白棋子交換,最優的情況就是每次都把黑白棋子移動到目標格子。
那么你的估價函數就出來了:
const 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}
};
inline int evaluate(){
R int cnt=0;
for(R int i=1;i<=5;i++)
for(R int j=1;j<=5;j++)
if(mp[i][j]!=goal[i][j])cnt++;
return cnt;
}
下面就是爆搜了:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cctype>
#define ll long long
#define R register
using namespace std;
template<typename T>inline void read(T &a){
char c=getchar();T x=0,f=1;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
a=f*x;
}
int n,m,t,mp[7][7],stx,sty,success;
char ch;
const int dx[]={0,1,1,-1,-1,2,2,-2,-2};
const int dy[]={0,2,-2,2,-2,1,-1,1,-1};
const 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}
};
inline int evaluate(){
R int cnt=0;
for(R int i=1;i<=5;i++)
for(R int j=1;j<=5;j++)
if(mp[i][j]!=goal[i][j])cnt++;
return cnt;
}
inline int safe(R int x,R int y){
if(x<1||x>5||y<1||y>5)return 0;
return 1;
}
inline void A_star(R int dep,R int x,R int y,R int maxdep){
if(dep==maxdep){
if(!evaluate())success=1;
return;
}
for(R int i=1;i<=8;i++){
R int xx=x+dx[i];
R int yy=y+dy[i];
if(!safe(xx,yy))continue;
swap(mp[x][y],mp[xx][yy]);
int eva=evaluate();
if(eva+dep<=maxdep)
A_star(dep+1,xx,yy,maxdep);
swap(mp[x][y],mp[xx][yy]);//回溯
}
}
int main(){
read(t);
while(t--){
success=0;
for(R int i=1;i<=5;i++){
for(R int j=1;j<=5;j++){
cin>>ch;
if(ch=='*')mp[i][j]=2,stx=i,sty=j;//記錄起點即為空白格子
else mp[i][j]=ch-'0';
}
}
if(!evaluate()){printf("0\n");continue;}
for(R int maxdep=1;maxdep<=15;maxdep++){
A_star(0,stx,sty,maxdep);
if(success){printf("%d\n",maxdep);goto ZAGER;}
}
printf("-1\n");
ZAGER:;
}
return 0;
}