• P4289 [HAOI2008]移动玩具


    传送门

    广搜

    4*4 的方阵只有 0 和 1

    显然可以状态压缩

    (如样例的开始状态压缩后就是1111000011100010)

    为了加快速度用了双向广搜(顺便学了一下双向广搜)

    双向广搜顾名思义

    就是从起点和终点两个方向广搜

    每次选择扩展步数少的扩展一层

    然后一旦一个状态被两边都找到了

    那就把两边的步数加一下,就是答案了

    然后要注意位运算的细节

    具体实现看代码吧

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    using namespace std;
    struct node
    {
        int p,stp;
    };//广搜的队列,p表示状态,stp表示当前走了几步
    queue <node> q[2];
    bool pd[100007][2];//记忆化,pd[i][]为到i状态需要的最少步数
    int ans,st,lst;
    inline void add(int k,int p,int stp)
    //尝试把状态p加入队列
    {
        if(pd[p][k^1]||(p==st&&k)||(p==lst&&k==0))//如果另一边已经找过了或者到了另一边的起点
        //因为在起点和终点的pd为0 所以要特判一下
        {
            ans=stp+pd[p][k^1];//更新ans
            return;//直接返回
        }
        if(pd[p][k]||p==st||p==lst) return;//如果找过了或者回到开始点,直接返回
    
        //否则
        pd[p][k]=stp;//更新pd
        node t; t.p=p; t.stp=stp;
        q[k].push(t);//加入队列
    }
    inline void bfs()
    {
        int k= q[1].size()<q[0].size();//确定要从哪一边扩展
        int now=q[k].front().stp;
        while(!q[k].empty())
        {
            node t=q[k].front();
            if(t.stp>now||ans) break;//保证一次只扩展一层
            q[k].pop();//(细节)要先判断再弹出
            for(int i=15;i>=0;i--)//向左移动
            {
                if(i%4==3) continue;//注意如果在边界就不能动
                if( !(t.p& (1<<i) ) || t.p& ( 1<<(i+1) ) ) continue;//判断
                add(k, t.p^ (1<<i) ^ ( 1<<(i+1) ), t.stp+1);//直接异或一波得到下一步的状态
            }
            for(int i=15;i>=0;i--)//向右
            {
                if(i%4==0) continue;//同样判断
                if( !(t.p& (1<<i) ) || t.p& (1<<(i-1) ) ) continue;
                add(k, t.p^ (1<<i) ^ ( 1<<(i-1) ), t.stp+1);
            }//同上
            for(int i=11;i>=0;i--)//向上,注意i的范围
            {
                if( !(t.p& (1<<i) ) || t.p& (1<<(i+4) ) ) continue;
                add(k, t.p^ (1<<i) ^ ( 1<<(i+4) ), t.stp+1);
            }//同上
            for(int i=15;i>=4;i--)//向上,同样注意i
            {
                if( !(t.p& (1<<i) ) || t.p& (1<<(i-4) ) ) continue;
                add(k, t.p^ (1<<i) ^ ( 1<<(i-4) ), t.stp+1);
            }//同上
        }
    }
    int main()
    {
        char ss[7]; memset(ss,0,sizeof(ss));
        for(int i=0;i<4;i++)
        {
            cin>>ss;
            for(int j=0;j<4;j++)
                st+=( (ss[j]-'0')<<(15-i*4-j) );
        }//读入状态
        node t; t.p=st; t.stp=0;
        q[0].push(t);//开始状态压入队列 
    
        for(int i=0;i<4;i++)
        {
            cin>>ss;
            for(int j=0;j<4;j++)
                lst+=( (ss[j]-'0')<<(15-i*4-j) );
        }
        t.p=lst; q[1].push(t);//同上
    
        if(st==lst)//特判一波起点和终点状态相同的情况
        {
            cout<<0;
            return 0;
        }
    
        while(!ans)
            bfs();//广搜找ans
        cout<<ans;
    }
  • 相关阅读:
    JS数组定义及详解
    JS中script词法分析
    JS函数 -- 功能,语法,返回值,匿名函数,自调用匿名函数,全局变量与局部变量,arguments的使用
    Java面试(1)-- Java逻辑运算符
    Java面试(3)-- Java关系运算符
    让 history 命令显示日期和时间
    mysql 权限管理
    docker基础
    docker 后台运行和进入后台运行的容器
    expect 自动输入密码
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9625819.html
Copyright © 2020-2023  润新知