题解 UVA1343 旋转游戏 The Rotation Game
IDA* 算法
IDA(带有估价函数的迭代加深) = ID(迭代加深dfs)+ A(估价函数)
听起来很高大上是吗?
其实很简单,咱们一个一个说。
所谓迭代加深,顾名思义,就是不断加深搜索深度。
在 dfs 的过程中会形成一颗搜索树,dfs 的特点就是找到一个子树就硬着头皮往下拱,不管拱到多深,这样的时间复杂度很容易炸。
有些题目中,我们能看出答案在搜索树中非常浅的位置,这样的话就可以从小到大限制搜索深度。
什么意思?
就是先假设最大深度为1,如果没搜到再设为2重新搜……以此类推
显然,会有一部分被重复遍历,但这点重复遍历与任凭其深度增大相比还是微不足道滴~
估价函数就是在迭代加深的基础上,我们估算一下最好情况下还需要走几步,如果当前步数+最好情况步数还超过限制,那就提前回溯。
对应到这道题,我们不断限制操作的步数进行迭代加深,并加入一个剪枝:减掉互逆操作(显然互逆操作没有意义……)
估价函数:最好情况下,每操作一次中间就能多一个我们想要的数,因此估价函数 = 8 - 中间最多的数的个数
注意实现的细节,具体看代码吧。
Code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
//输入的时候给每个位置都标上号
const int opt[8][7]=//每一步操作拉动了那些点
{
{22,20,15,11,6,2,0},
{23,21,17,12,8,3,1},
{4,5,6,7,8,9,10},
{13,14,15,16,17,18,19},
{1,3,8,12,17,21,23},
{0,2,6,11,15,20,22},
{19,18,17,16,15,14,13},
{10,9,8,7,6,5,4}
};
const int oppo[8]={5,4,7,6,1,0,3,2};//逆操作(剪枝用)
const int cen[8]={6,7,8,11,12,15,16,17};//中间的点
int mp[24],ans[10005];//ans记录答案
bool flag;//游戏结束标记
inline int f()//估价函数
{
int tot[4]={0},res=8;
for(int i=0;i<8;i++) tot[mp[cen[i]]]++;
for(int i=1;i<=3;i++){
res=min(res,8-tot[i]);
}
return res;
}
inline void dfs(int x,int dep,int last)
{
if(flag) return ;
if(x-1+f()>dep) return ;
if(x>dep){
if(!f()){
flag=true;
for(int i=1;i<=dep;i++){
cout<<(char)(ans[i]+'A');
}
cout<<endl;
cout<<mp[6]<<endl;
}
return ;
}
for(int i=0;i<8;i++){
if(last!=-1&&i==oppo[last]) continue;//互逆操作
int raw=mp[opt[i][6]];
for(int j=6;j>=1;j--){
mp[opt[i][j]]=mp[opt[i][j-1]];
}
mp[opt[i][0]]=raw;//旋转
ans[x]=i;
dfs(x+1,dep,i);
raw=mp[opt[i][0]];
for(int j=0;j<6;j++){
mp[opt[i][j]]=mp[opt[i][j+1]];
}
mp[opt[i][6]]=raw;//回溯,再倒着转回去
}
}
int main()
{
while(cin>>mp[0]){
if(!mp[0]) return 0;
flag=false;
for(int i=1;i<=23;i++){
cin>>mp[i];
}
if(!f()){
puts("No moves needed");
cout<<mp[6]<<endl;
continue;
}
for(int i=1;i<=100000;i++){
dfs(1,i,-1);
if(flag) break;
}
}
return 0;
}