zoukankan      html  css  js  c++  java
  • [SCOI2005]骑士精神

    题目描述

    在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空位上。 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步数完成任务。

    img

    输入格式

    第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,*表示空位。两组数据之间没有空行。

    输出格式

    对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。


    不难想到搜索可以做这题。由于骑士只能往空格走,与其枚举每个骑士可以走的方向不如直接搜索空格往哪走。我们尝试用深搜做这题。

    很容易想出状态:当前的棋盘,空格的位置,走的步数。棋盘可以开个全局变量来存。每次空格可以往8个方向走,只需往8个方向分别拓展即可。

    考虑剪枝:

    1.上下界剪枝:

    上界:题目给的是15次。

    下界:最好的情况莫过于跳

    [sum_{i=1}^{n}sum_{j=1}^{n}[g[i][j]≠des[i][j]] ]

    次了。虽然这对剪枝没什么用

    2.搜索顺序:由于每个格子可以重复走,所以顺序随意。

    3.排除等效冗余:往前走了之后又往回走是没有意义的,所以我们应该避免走上一次走的方向的反方向。

    4.最优化剪枝:如果当前搜到的答案为ans,那么当步数≥ans时就没必要往下搜了,回溯。

    5.记忆化:没什么好记的,而且记了容易MLE。跳过。

    有了这么多剪枝,你的搜索已经很快了,但仍然不尽人意。由于本题的答案很小,我们考虑进一步优化。

    把DFS加上迭代加深。从1开始枚举搜索深度,当搜索到答案时必定是最优解,所以最优化剪枝就没必要做了。如果搜到了上界15,那么就是无解。于是我们的上下界剪枝也没有必要做了。

    然而IDFS仍然不够快,因为这题的答案也并不像某些ans≤5那么小,当我们的深度枚举到ans时,我们面对的搜索树其实是非常大的,然而剪枝已经减不掉了。

    根据IDFS的性质,像广搜一样,当第一次搜到答案时就是最优解,直接回溯。那我们管它多大的搜索树,我们只让程序快点搜出答案,即优先往答案所在的方向走即可。这就要用到启发式搜索IDA * 了。

    设计估价函数。由于估价函数必须小于等于实际值,我们可以结合之前的下界来设计。也就是说,估价函数就是当前状态与目标状态不同的格子数:

    [f(g[][])=sum_{i=1}^{5}sum_{j=1}^{5}[g[i][j]≠des[i][j]] ]

    当当前深度加上预估值大于了搜索深度时就可以不往下搜了。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define maxn 6
    using namespace std;
    
    const int dir[8][2]={{1,2},{2,1},{2,-1},{1,-2},{-1,2},{-2,1},{-2,-1},{-1,-2}};
    const int des[maxn][maxn]={{},{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 g[maxn][maxn],ans;
    
    inline int evaluate(){
        int cnt=0;
        for(register int i=1;i<=5;i++) for(register int j=1;j<=5;j++) if(g[i][j]!=des[i][j]) cnt++;
        return cnt;
    }
    inline bool check(const int &x,const int &y){ return x<1||5<x||y<1||5<y; }
    void IDAstar(int x,int y,int dep,int pre_dir,const int &lim){
        if(dep==lim){ if(!evaluate()) ans=true; return; }
        for(register int i=0;i<8;i++) if(i+pre_dir!=7){
            int tx=x+dir[i][0],ty=y+dir[i][1];
            if(!check(tx,ty)){
                swap(g[x][y],g[tx][ty]);
                if(evaluate()+dep<=lim) IDAstar(tx,ty,dep+1,i,lim);
                swap(g[x][y],g[tx][ty]);
                if(ans) return;
            }
        }
    }
    
    int main(){
        int t,sx,sy; scanf("%d",&t);
        while(t--){
            for(register int i=1;i<=5;i++){
                for(register int j=1;j<=5;j++){
                    char in; cin>>in;
                    if(in=='*') g[i][j]=2,sx=i,sy=j;
                    else g[i][j]=in-'0';
                }
            }
    
            if(!evaluate()){ puts("0"); continue; }
            ans=false;
            for(register int maxdep=1;maxdep<=15;maxdep++){
                IDAstar(sx,sy,0,-1,maxdep);
                if(ans){ printf("%d
    ",maxdep); goto Lzs; }
            }
            puts("-1");
            Lzs:;
        }
        return 0;
    }
    
  • 相关阅读:
    购物车程序
    python学习第二节 数据类型、字符编码、文件处理
    while实现2-3+4-5+6...+100 的和
    给文件加权限
    查询数据插入新表格
    归档程序错误。在释放之前仅限于内部连接
    查看Linux环境变量
    查找文件命令
    ORACLE导入导出操作篇
    oracle中使用minus进行数据排除(类似SqlServer except函数)
  • 原文地址:https://www.cnblogs.com/akura/p/10877789.html
Copyright © 2011-2022 走看看