HDU - 1429 bfs()+简单状态压缩的详细解法

本文介绍如何使用状态压缩和广搜算法解决含有钥匙和门的问题,通过二进制数表示钥匙状态,利用位运算判断和更新钥匙,实现迷宫寻路。详细解析了位运算在状态压缩中的应用,包括与、或、异或和移位操作,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


HDU - 1429 的原题链接( http://acm.hdu.edu.cn/showproblem.php?pid=1429)

(一) 题目的大概思路

本题中我们要用到状态压缩的处理,将本题处理为一个朴素的广搜。本文参考了改博主的一些思想 谢谢该博主的思路
我们用到了哈希的思想。(二进制数中的0对应没有,1对应有)比如,假设我们有4种钥匙 A,B,C,D. 假设开始一把钥匙都没有,对应十进制数0, 在二进制的储存时,为0 0 0 0,对应一把钥匙都没有。
再比如有二进制数, 0 0 0 1,就好比 A,B,C,D中,我有A钥匙,没有B,C,D钥匙。因为正好二进制数0 0 0 1,有一个十进制数与其对应 ,再好比,我有钥匙A,B,C,但没有D,那我们就可以用 0 1 1 1,这个二进制数来对应,同时,又有一个十进制数的二进制储存正好为这个二进制数。所以这正好就形成了一种哈希。由于对于每一种钥匙 我们有 (有和没有)两种情况,故 我们至少需要 2n个十进制数来对应。 (比如 有4种钥匙 我们有0000,0001,0011…1111等16种情况)

(二)注意事项

这个题不同于朴素的广搜的原因是,因为有钥匙和门的存在,因为必须要有钥匙才能够开门。
当然,我们之前考虑到,每一个十进制数的二进制储存,恰好对应了我们手中的钥匙的状态,但实际上,我们并不能直接访问的对十进制数的二进制信息。那怎样才能够达到访问,或者修改二进制信息的目的呢?
恰好我们有位运算 ,当然,我们需要一点位运算的基础,所以我要感谢这名博主在此引用 他的博文。下面属于 直接copy这名博主的博文:
为了更好的理解状压dp,首先介绍位运算相关的知识。

1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。

2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。

3.’’符号,xy,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)^2(10)=1(01)。

4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。

这四种运算在状压dp中有着广泛的应用,常见的应用如下:

1.判断一个数字x二进制下第i位是不是等于1。(可以用于判断我们是否拥有对应门的钥匙)

方法:if ( ( ( 1 << ( i - 1 ) ) & x ) > 0)

将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。

2.将一个数字x二进制下第i位更改成1。(可以让我们改变手中钥匙的状态)

方法:x = x | ( 1<<(i-1) )

证明方法与1类似,此处不再重复证明。

3.把一个数字二进制下最靠右的第一个1去掉。

方法:x=x&(x-1)

这样我们就能够处理门与钥匙了,因为我们看到门时,必须判断我们,手里是否有钥匙,只有拥有对应门钥匙我们才能够开门。同时,我们在找到 每一把钥匙的时候,我们必须更改我们手中钥匙的状态,比如开始我们手中没有一把钥匙,为0000,但我们找到A钥匙后,我们想变为0001,那么我们通过位运算就能够处理好了。

(三) 代码

#include <algorithm>
#include <queue>
#include <string.h>
#include <math.h>
#include <iostream>
#include <stdio.h>
#define ll long long

#define inf 0x3f3f3f3f
const int maxn = 27;
using namespace std;
int n,m,T;  
int beginx,beginy,endx,endy;   //begin 记录起点。 end 记录终点。
int d[4][2]= { {-1,0},{1,0},{0,1},{0,-1}};  //行走方向
struct Node
{
    int x,y,key,step;   
    //key 这样十进制数用来哈希手中钥匙状态step用来记录步数
};
bool vis[maxn][maxn][1<<11];   //vis 用来标记,看是否走过
//虽然 A-J只有10把钥匙,但是保险开 2的11次方种情况
char mymap[maxn][maxn];  //记录我的图
bool check(Node next)   //check 函数用来判断可不可以走
{
    if(next.x<0||next.x>=n) return false;    //判断是否越界
    if(next.y<0||next.y>=m) return false;  //判断是否越界
    if(mymap[next.x][next.y]=='*') return false; //是障碍就退出
    if(vis[next.x][next.y][next.key]) return false;   //走过就退出
    if(isupper(mymap[next.x][next.y]))  
               // ctype.h 头文件的一个函数,用来判断是否是字母大写
    {
        if(((1 <<(mymap[next.x][next.y]-'A')) & next.key ) == 0)   //位运算,判断是否有钥匙
        return false;   //没钥匙就退出
    }
    return true;
}
void bfs()
{
    memset(vis,0,sizeof(vis));   //很关键,,每次将标记归零,,血的教训
    queue<Node>q;
    q.push( {beginx,beginy,0,0} ); //把起点放进队列
    vis[beginx][beginy][0]=true; //标记起点
    while(!q.empty())
    {
        Node t= q.front();
        q.pop();
        if(t.x==endx && t.y==endy)    //到达终点结束
        {
            if(t.step >= T)  printf("-1\n");  //超时
            else printf("%d\n", t.step);  //逃出
            return;
        }
        for(int i=0; i<4; i++)
        {
            Node temp = {t.x+d[i][0],t.y+d[i][1] ,t.key,t.step+1};  //上下左右都走一遍,步数+1
            if( islower(mymap[temp.x][temp.y]))  
                   // ctype.h 头文件的一个函数,用来判断是否是字母小写
            {
                temp.key|= 1 << (mymap[temp.x][temp.y]-'a');   
                // 位运算,改变钥匙状态,添加这把钥匙
            }

            if(check(temp))
            {
                vis[temp.x][temp.y][temp.key]=true;
                q.push(temp);
            }

        }

    }
    printf("-1\n");   //找不到终点的非正常情况的输出

}

int main()
{
    while(scanf("%d%d%d",&n,&m,&T)!=EOF)
    {
        for(int i=0; i<n; i++)
        {
            getchar();
            for(int j=0; j<m; j++)
            {
                mymap[i][j]=getchar();    //存图
                if(mymap[i][j]=='@') beginx=i,beginy=j;  //记录起点
                else if(mymap[i][j]=='^') endx=i,endy=j; //记录终点
            }
        }
        bfs();
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值