UVA 1601 Morning after holloween(搜索前的剪枝)

【题意】

         

               w*h(w、h<=16)网格上有n(n<=3)个小写字母(代表鬼)。要求把他们   分别移动到对应的大写字

      母里。每步可以有多个鬼同时移动(可以往上下左右移动或不动),但每步结束之后任何两个鬼不能占用同

     一个位置,也不能在一步之内交换位置。任意一个2*2子网格中至少有一个‘#’,鬼不能再‘#’上移动。输出最

     少的步数,保证有解。


【分析】

        此题用一般的bfs做肯定超时,刚开始想的有几个鬼就几次bfs,用数组标记每个鬼走的点的步数,最后相

   加。后来仔细想了想这样做是非常麻烦且很难达到目标的。

         1. 题目指出任意一个2*2子网格中最少有一个‘#’,表示这输入的网格中有很多‘#’,真正能走的路很少,所以

  新定义一个数组,用来保存能走的点及其能走到哪几个点。

         2.  表示坐标得用2个变量表示,有点麻烦,将坐标压缩成一个整数,方便使用和移动。

         3. 题目有多个鬼同时移动,因为无法事先知道有几个鬼,不然就令鬼为3个,节约讨论的代码,省掉很多代

   码。

         4. 将3个鬼的坐标通过位运算压缩到1个整数里,方便搜索过程。

  

【代码】      


方法一:单向BFS

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,k;
int book[200][200][200],G[200][5];
int deg[200];
int s[3],t[3];
int fang[5][2]={0,0,-1,0,0,-1,0,1,1,0};

inline bool fun(int a,int b,int a2,int b2)
{
    return (a2==b2)||(a==b2&&b==a2); // 判断是否走到同一点或者两点交换了
}

inline int ID(int a,int b,int c)
{
    return (a<<16) | (b<<8) | c; // 将三个坐标合成一个数
}

int BFS()
{
    memset(book,-1,sizeof(book));
    queue<int>p;
    p.push(ID(s[0],s[1],s[2]));
    book[s[0]][s[1]][s[2]]=0;
    while(!p.empty())
    {
        int u=p.front();
        p.pop();
        int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff; // 将合并的点拆分出来,进行移动
        if(a==t[0]&&b==t[1]&&c==t[2])
            return book[a][b][c];
        for(int i=0;i<deg[a];i++)
        {
            int a2=G[a][i];
            for(int j=0;j<deg[b];j++)
            {
                int b2=G[b][j];
                if(fun(a,b,a2,b2))
                    continue;
                for(int k=0;k<deg[c];k++)
                {
                    int c2=G[c][k];
                    if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
                        continue;
                    if(book[a2][b2][c2]==-1)
                    {
                        book[a2][b2][c2]=book[a][b][c]+1;
                        p.push(ID(a2,b2,c2));
                    }
                }
            }
        }
    }
    return -1;
}

int main()
{
    while(scanf("%d %d %d",&m,&n,&k)&&(n+m+k))
    {
        getchar();
        memset(G,0,sizeof(G));
        memset(deg,0,sizeof(deg));
        char e[20][20];
        for(int i=0;i<n;i++)
            fgets(e[i],18,stdin);
        int cnt=0,id[20][20]={0},x[200]={0},y[200]={0};
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(e[i][j]!='#') // 将不是'#'的数据分离出来,节约搜索时间,不然超时
                {
                    x[cnt]=i;
                    y[cnt]=j;
                    id[i][j]=cnt;
                    if(e[i][j]>='a'&&e[i][j]<='c')
                        s[e[i][j]-'a']=cnt;
                    else if(e[i][j]>='A'&&e[i][j]<='C')
                        t[e[i][j]-'A']=cnt;
                    cnt++;
                }
            }
        }
        for(int i=0;i<cnt;i++) // 进行非‘#’位置的移动,判断可以的的方向的个数及其坐标,节约时间
        {
            deg[i]=0;
            for(int j=0;j<5;j++)
            {
                int tx,ty;
                tx=x[i]+fang[j][0];
                ty=y[i]+fang[j][1];
                if(e[tx][ty]!='#')
                    G[i][deg[i]++]=id[tx][ty];
            }
        }
        // 如果不够3个就补上,方便进行操作,不然还得进行3种讨论
        if(k<=2)
        {
            deg[cnt]=1;
            G[cnt][0]=cnt;
            s[2]=t[2]=cnt++;
        }
        if(k<=1)
        {
            deg[cnt]=1;
            G[cnt][0]=cnt;
            s[1]=t[1]=cnt++;
        }
        printf("%d\n",BFS());
    }
    return 0;
}

方法二:双向BFS

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,k;
int book[200][200][200],vis[200][200][200],G[200][5];
int deg[200];
int s[3],t[3];
int fang[5][2]= {0,0,-1,0,0,-1,0,1,1,0};

inline bool fun(int a,int b,int a2,int b2)
{
    return (a2==b2)||(a==b2&&b==a2); // 判断是否走到同一点或者两点交换了
}

inline int ID(int a,int b,int c)
{
    return (a<<16) | (b<<8) | c; // 将三个坐标合成一个数
}

int BFS()
{
    memset(book,0,sizeof(book));
    memset(vis,0,sizeof(vis));
    queue<int>p,q;
    p.push(ID(s[0],s[1],s[2]));
    q.push(ID(t[0],t[1],t[2]));
    book[s[0]][s[1]][s[2]]=0;
    book[t[0]][t[1]][t[2]]=1;
    vis[s[0]][s[1]][s[2]]=1;
    vis[t[0]][t[1]][t[2]]=2;
    while(!p.empty()||!q.empty())
    {
        int len1=p.size();
        while(len1--)   // 双向BFS是按层搜索的,不然会出错,可能一个环上面要4步,下面要3步,如果正反按步搜索,你走一步我走一步就会出错
        {
            int u=p.front();
            p.pop();
            int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff; // 将合并的点拆分出来,进行移动
            for(int i=0; i<deg[a]; i++)
            {
                int a2=G[a][i];
                for(int j=0; j<deg[b]; j++)
                {
                    int b2=G[b][j];
                    if(fun(a,b,a2,b2))
                        continue;
                    for(int k=0; k<deg[c]; k++)
                    {
                        int c2=G[c][k];
                        if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
                            continue;
                        if(vis[a2][b2][c2]==0)
                        {
                            vis[a2][b2][c2]=1;   // 正标记
                            book[a2][b2][c2]=book[a][b][c]+1;
                            p.push(ID(a2,b2,c2));
                        }
                        else if(vis[a2][b2][c2]==2) // 如果反向走过此点,就说明构成了最短路
                            return book[a][b][c]+book[a2][b2][c2];
                    }
                }
            }
        }
        int len2=q.size();
        while(len2--)
        {
            int u=q.front();
            q.pop();
            int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff; // 将合并的点拆分出来,进行移动
            for(int i=0; i<deg[a]; i++)
            {
                int a2=G[a][i];
                for(int j=0; j<deg[b]; j++)
                {
                    int b2=G[b][j];
                    if(fun(a,b,a2,b2))
                        continue;
                    for(int k=0; k<deg[c]; k++)
                    {
                        int c2=G[c][k];
                        if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
                            continue;
                        if(vis[a2][b2][c2]==0)
                        {
                            vis[a2][b2][c2]=2;   // 反标记
                            book[a2][b2][c2]=book[a][b][c]+1;
                            q.push(ID(a2,b2,c2));
                        }
                        else if(vis[a2][b2][c2]==1) // 如果正向走过此点,就说明构成了最短路
                            return book[a][b][c]+book[a2][b2][c2];
                    }
                }
            }
        }
    }
    return -1;
}

int main()
{
    while(scanf("%d %d %d",&m,&n,&k)&&(n+m+k))
    {
        getchar();
        memset(G,0,sizeof(G));
        memset(deg,0,sizeof(deg));
        char e[20][20];
        for(int i=0; i<n; i++)
            fgets(e[i],18,stdin);
        int cnt=0,id[20][20]= {0},x[200]= {0},y[200]= {0};
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                if(e[i][j]!='#') // 将不是'#'的数据分离出来,节约搜索时间,不然超时
                {
                    x[cnt]=i;
                    y[cnt]=j;
                    id[i][j]=cnt;
                    if(e[i][j]>='a'&&e[i][j]<='c')
                        s[e[i][j]-'a']=cnt;
                    else if(e[i][j]>='A'&&e[i][j]<='C')
                        t[e[i][j]-'A']=cnt;
                    cnt++;
                }
            }
        }
        for(int i=0; i<cnt; i++) // 进行非‘#’位置的移动,判断可以的的方向的个数及其坐标,节约时间
        {
            deg[i]=0;
            for(int j=0; j<5; j++)
            {
                int tx,ty;
                tx=x[i]+fang[j][0];
                ty=y[i]+fang[j][1];
                if(e[tx][ty]!='#')
                    G[i][deg[i]++]=id[tx][ty];
            }
        }
        // 如果不够3个就补上,方便进行操作,不然还得进行3种讨论
        if(k<=2)
        {
            deg[cnt]=1;
            G[cnt][0]=cnt;
            s[2]=t[2]=cnt++;
        }
        if(k<=1)
        {
            deg[cnt]=1;
            G[cnt][0]=cnt;
            s[1]=t[1]=cnt++;
        }
        printf("%d\n",BFS());
    }
    return 0;
}


【收获】

           

            1. 当无用数据很多时,可以选择重新构建数组,只保存有用数据,节约搜索时间。

           2. 搜索保存周围能走的点,结尾搜索时间,不过得用一个数组保存每个点能走到几个点,对可走方向比较少的数据用,否则此方法没太大作用。

           3. 数据有部分关系,且范围不是太大,可以选择压缩到一个数据里面。

           4. 双向BFS,双向BFS必须按层走,正向走完所有当前一步,再反向走完所以当前一步,不然会出错,例如S-->T,从上面走需要过3个点、4步,从下面走需要过2个点、3步,如果按点走的话,如果先从上面走就会出现错误。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值