UVa1601万圣节后的早晨

本题并不是我自己想出来的,仅仅只是看懂了刘汝佳的源代码,然后自己想着敲了一遍,也许我写得不好,不过只要对大家有点帮助就还行。


本题运用到几个小技巧和大家分享一下:

1)添加虚拟节点,使它更方便BFS。

2)用二进制映射状态,方便入队和出队。

3)使用数组来变换方向


另外,假如你没有进行优化直接用原图BFS,那么肯定是会超时的,在这里就有一种优化方法。可以将原图存好后把字母和空格单独剔出来,因为‘#’它是不访问的。这样就可以减少你搜索的次数,以此来优化。还有就是在BFS时,如果用原数据BFS也肯定是会超时的,这时我们就可以用二进制来进行状态压缩,以此来减少很多入队和出队的时间。


下面是我的代码和注释


#include<cstdio>
#include<queue>
#include<cstring>
#include<cctype>
using namespace std;

const int maxn = 150;///空格占大约75%
const int maxs = 20;
const int dx[]={1,-1,0,0,0};///用于行上的位置变化
const int dy[]={0,0,1,-1,0};///用于列上的位置变化

int n,m,z,id[maxs][maxs];
int s[3],t[3],G[maxn][5],dig[maxn];

int d[maxn][maxn][maxn];///表示当前状态到原点的距离

int ID(int k1,int k2,int k3){
    return (k1<<16)|(k2<<8)|k3;///用二进制压缩
}

bool pd(int a,int b,int a2,int b2){
    return a2 == b2 || (a2 == b && b2 == a);
}

int bfs(){
    queue<int >q;
    memset(d,-1,sizeof(d));
    q.push(ID(s[0],s[1],s[2]));///使三个初始节点映射成一个值,方便入队和出对
    d[s[0]][s[1]][s[2]] = 0;///第一个状态到自己,距离为零
    while(!q.empty()){
        int u = q.front();q.pop();
        int a = (u>>16)&0xff,b = (u>>8)&0xff,c = u&0xff;///分离出之前的三个节点
        if(a == t[0] && b == t[1] && c == t[2])return d[a][b][c];///判断是否到达目标节点
        for(int i = 0;i < dig[a]; i++){
            int a2 = G[a][i];///第一个节点能到的节点
        for(int j = 0;j < dig[b]; j++){
                int b2 = G[b][j];///第二个节点能到的节点
            if(pd(a,b,a2,b2))continue;///判断是否合法
        for(int k = 0;k < dig[c]; k++){
                int c2 = G[c][k];///第三个节点能到的节点
            if(pd(a,c,a2,c2))continue;///判断是否合法
            if(pd(b,c,b2,c2))continue;///判断是否合法
            if(d[a2][b2][c2] != -1)continue;///剪枝
            d[a2][b2][c2] = d[a][b][c] + 1;///移动一次
            q.push(ID(a2,b2,c2));///移动后映射入队
        }
        }
        }
    }
    return -1;
}

int main(){
    while(scanf("%d%d%d",&n,&m,&z) == 3 && n){
        char aa[20][20];
        for(int i1 = 0;i1 <= m;i1++)
              fgets(aa[i1],20,stdin);
        ///可以将空格也一起读入
        int x[maxn],y[maxn];
        int cnt = 0;///可以记录节点编号
        for(int i = 0;i < m; i++){///行
        for(int j = 0;j < n; j++){///列
            if(aa[i][j] != '#'){
                x[cnt] = i;       ///记录该节点在那行
                y[cnt] = j;       ///记录该节点在那列
                id[i][j] = cnt;   ///用一个二维数组把节点编号存下来
                if(islower(aa[i][j])){      ///判断该字符是否为小写字母
                    s[aa[i][j] - 'a'] = cnt;///记录给该字母的节点编号
                }
                else if(isupper(aa[i][j])){ ///判断该字符是否为大写字母
                    t[aa[i][j] - 'A'] = cnt;
                }
                cnt++;
            }
        }
        }
        for(int i = 0;i < cnt; i++){    ///开始遍历每个节点
                dig[i] = 0;             ///用来存能与该节点相连的节点的数量
            for(int dir = 0;dir < 5; dir++){    ///方向变化
                int nx = x[i] + dx[dir],ny = y[i] + dy[dir];///变化后的横纵坐标
                if(aa[nx][ny] != '#'){
                    G[i][dig[i]++] = id[nx][ny];    ///记录该节点与他能到的节点,以此建图。
                }
            }
        }
        if(z <= 2){dig[cnt] = 1;G[cnt][0] = cnt;s[2] = t[2] = cnt++;}///添加虚拟节点
        if(z <= 1){dig[cnt] = 1;G[cnt][0] = cnt;s[1] = t[1] = cnt++;}///添加虚拟节点
        printf ("%d\n",bfs());
    }
}

希望对你有帮助。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值