十五数码问题(IDA*)

这个题可真是快调死我了

题面很简单,就是十五个数字的华容道,然后问最少几步可以还原成初始状态
(貌似是人工智能经典问题?

有了八数码问题的经验,我们知道这玩意肯定是要各种玄学做法(比如什么要人命的康托展开双向广搜\(A*\)迭代加深\(IDA*\) \(bulabula\)~

康托展开就算了吧,那玩意真心懒得写,而且十五数码问题用这种东西貌似不合适(其实是不会

\(A*\)写起来太麻烦,于是写写\(IDA*\)罢,貌似这题双向广搜会快一些?

其实\(IDA*\)也没有很慢好吧 (不开\(O_2\)根本过不了

对于估价函数,我们可以考虑曼哈顿距离,估价函数的值等同于当前状态下所有非 \(0\) 的数字到目标位置的曼哈顿距离之和。

而且对于\(15\)数码问题,我们可以得出以下结论:十五数码和八数码判断是否有解的方法不同,八数码\(0\)的移动不影响其余\(7\)个数字逆序数的奇偶性,而十五数码\(0\)的左右移动不影响其余\(15\)个数逆序数的奇偶性 (顺序不变),但上下移动改变奇偶(移动三次),加上\(0\)的话\(16\)个数逆序数左右改变(移动一次),上下也改变(移动\(7\)次),需要注意每次移动\(0\)的距离奇偶性也改变(\(0\)到目标位置的曼哈顿距离不是加1就是减一),所以\(16\)个数逆序数与\(0\)的距离之和\(s\)的奇偶性不因\(0\)的滑动而改变,初始时\(s\)是奇数,所以只有\(s\)是奇数的状态才是可到达的.

于是我们在搜之前先判一下有没有解:

inline bool check(){//check it is the ans or not
    int cnt=0,tot=0;
    for (int i=1;i<=4;++i){
    	for (int j=1;j<=4;++j){
    		puz[tot]=tmp[i][j];
    		tot++;
		}
	}
    for (int i=0;i<16;++i){
        if (puz[i]==0)
            cnt+=3-i/4;
        else{
            for (int j=0;j<i;++j){
            	if (puz[j] && puz[j]>puz[i]){
            		cnt++;
				}
			}
        }
    }
    return !(cnt&1);
}

之后我们还需要加入一个剪枝:永远不要走和上一步相反的路;

大概就这些

//#define LawrenceSivan

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define re register

int x,y,cnt; 
int tmp[5][5];
int puz[16]; 

const int goal[5][5]={
	{0,0,0,0,0},
	{0,1,2,3,4},
	{0,5,6,7,8},
	{0,9,10,11,12},
	{0,13,14,15,0}
};

const int dx[4]={0,1,-1,0};
const int dy[4]={1,0,0,-1};

const int px[16]={4,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4};
const int py[16]={4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3};

inline int mabs(int a){
	return a>0?a:-a;
}

inline bool check(){//check it is the ans or not
    int cnt=0,tot=0;
    for (int i=1;i<=4;++i){
    	for (int j=1;j<=4;++j){
    		puz[tot]=tmp[i][j];
    		tot++;
		}
	}
    for (int i=0;i<16;++i){
        if (puz[i]==0)
            cnt+=3-i/4;
        else{
            for (int j=0;j<i;++j){
            	if (puz[j] && puz[j]>puz[i]){
            		cnt++;
				}
			}
        }
    }
    return !(cnt&1);
}

inline int evaluate(){//judge it can get the ans or not
    int res=0;
    for(int i=1;i<=4;i++){
    	for(int j=1;j<=4;j++){
    		if(tmp[i][j]==0)continue;
       	 	res+=mabs(i-px[tmp[i][j]])+mabs(j-py[tmp[i][j]]);
    	}
    }
    return res;
} 

inline int getway(int i){
	if(dx[i]==-1)return 2;
	if(dx[i]==1)return 1;
	if(dy[i]==-1)return 4;
	if(dy[i]==1)return 3;
}

inline bool test(int i,int pre){
	if(dx[i]==-1&&pre==1)return true;
	if(dx[i]==1&&pre==2)return true;
	if(dy[i]==-1&&pre==3)return true;
	if(dy[i]==1&&pre==4)return true;
	
	return false;
}

bool IDA_star(int step,int x,int y,int pre){//do it
	int eva=evaluate();
	if(!eva)return true;
	
	if(step+eva>cnt)return false;
	
	for(int i=0;i<4;i++){ 
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx>4||xx<1||yy>4||yy<1||test(i,pre))continue;
        swap(tmp[xx][yy],tmp[x][y]);
        if(IDA_star(step+1,xx,yy,getway(i)))return true;		
		swap(tmp[xx][yy],tmp[x][y]);
    }
    return false;
}

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
	return x*f;
}

int main(){
#ifdef LawrenceSivan
	freopen("puzzle.in","r",stdin);
	freopen("puzzle.out","w",stdout);
#endif
	for(re int i=1;i<=4;i++){
		for(re int j=1;j<=4;j++){
			tmp[i][j]=read();
			if(tmp[i][j]==0)x=i,y=j;
		}
	}
	/*for(re int i=1;i<=4;i++){
		for(re int j=1;j<=4;j++){
			cout<<tmp[i][j]<<" ";
		}
		cout<<endl;
	}*/
	
	if(!evaluate()){//if the begin is the end
        printf("%d\n",0);
        return 0;
    }
    
    if(!check()){//if the begin is the end
        printf("No\n");
        return 0;
    }
    
    while(++cnt){
    	if(IDA_star(0,x,y,0)){
            printf("%d\n",cnt);
            return 0;
        }
        if(cnt>50){
        	printf("No\n");
			return 0;	
		}
	}
    
    return 0;
}

posted @ 2021-04-20 17:34  LawrenceSivan  阅读(1231)  评论(0编辑  收藏  举报