二进制迷宫 贪心 BFS

搜索专项训练赛 二进制迷宫

题目大意

给出一个n*m的图,数字要么是0要么是1。从左上角坐标为(1,1)的格子出发,走到右下角的坐标为(n,m)的格子,可以沿上下左右四个方向行走。每到一个格子,就记录下里面的数字。到达终点的时候,将得到一个由0和1构成的序列,把这个序列看做一个二进制数(可以含前导零)。要求这个二进制数尽可能小,计算并输出这个二进制数。

数据范围

对于30%的数据:1≤n,m≤100
对于100%的数据:1≤n,m≤1000


考试的时候并没有说是搜索专项训练赛,所以一开始想到DP是很正常的,竟然有同学使用了string类型的DP数组,真是开了眼界(然而这样AC不了)。

首先容易想到,去掉前导零后,这个二进制数的位数要尽量少,在保证位数最少的情况下,尽量让高位选0。这个贪心是显然正确的,但是怎样知道位数最小值是多少?从哪里开始贪心?

题目中上下左右都可以行走,如果是DP的话状态不是很好转移。如果说只能向下或向右走容易让我们想到DP,那么多个方向都能行走容易让我们想到BFS

其实,“只能向下或向右走”也能指向正解。如果当前已经有1出现了,那么就只能向下或向右走了,因为不这样做会使位数增多。注意到在只能向下或向右走的前提下,走到终点的步数是一定的,是能够算出来的。保证位数最少,可以通过BFS找到离终点“最近”的一个或者多个0。

接下来就要从这些0出发找到最优解。只能向下或向右走,此时当然可以用DP了,但是用string类型的DP数组?其实还是BFS就可以了。开一个答案数组,记录答案的每一位上填0还是填1,这样就可以剪掉上一步不可能达到最优解的情况,具体实现见代码。

时间复杂度 O(mn)


代码:

#include<stdio.h>
#include<queue>
#include<algorithm>
#define MAXN 1005
using namespace std;


int N,M,id[MAXN][MAXN];
char Map[MAXN][MAXN];
int Ans[MAXN*2];

bool vis[MAXN][MAXN];

struct node{int x,y,step,v;};
//x,y表示位置,step记录从符合要求的0开始已经走了多少步,v记录上一步的权值
queue<node>Q;
int Max=1;

int dx[4]={1,0,0,-1},dy[4]={0,1,-1,0};

void BFS()
{
    vis[1][1]=true;
    if(Map[1][1]=='1')return;

    node tmp,head;
    tmp.x=1;tmp.y=1;

    Q.push(tmp);

    int k,a,b,ta,tb;

    while(Q.size())
    {
        head=Q.front();Q.pop();
        a=head.x;b=head.y;
        for(k=0;k<4;k++)
        {
            ta=a+dx[k];tb=b+dy[k];
            if(!vis[ta][tb]&&Map[ta][tb]=='0'&&ta&&tb&&ta<=N&&tb<=M)
            {
                vis[ta][tb]=true;
                Max=max(Max,ta+tb-1);//找“最靠近终点”的0
                tmp.x=ta;tmp.y=tb;
                Q.push(tmp);
            }
        }
    }
}

bool mark[MAXN][MAXN];
void GetAns()
{
    int i,j,x,y,a,b,ta,tb,tv;
    node tmp,head;

    for(i=1;i<=N;i++)
    {
        x=i;y=Max-x+1;
        if(vis[x][y]&&y>0&&x>0&&y<=M)
        {
            tmp.x=x;tmp.y=y;tmp.step=0;tmp.v=0;
            Q.push(tmp);
        }
    }//多个满足条件的0,全部入队

    while(Q.size())
    {
        head=Q.front();Q.pop();
        if(head.v>Ans[head.step])continue;//剪掉不能得到最优解的情况
        a=head.x;b=head.y;
        for(i=0;i<2;i++)
        {
            ta=a+dx[i];tb=b+dy[i];
            if(ta&&tb&&ta<=N&&tb<=M)
            {
                tv=Map[ta][tb]-'0';
                if(Ans[head.step+1]>=tv)
                {
                    Ans[head.step+1]=tv;
                    tmp.step=head.step+1;
                    tmp.x=ta;tmp.y=tb;
                    tmp.v=tv;
                    if(!mark[ta][tb])Q.push(tmp),mark[ta][tb]=true;//防止同一个点多次进队
                }
            }
        }
    }
}

int main()
{
    int i,j,tot=0;

    scanf("%d%d",&N,&M);
    for(i=1;i<=N;i++)scanf("%s",&Map[i][1]);

    BFS();

    for(i=0;i<=N+M-Max;i++)Ans[i]=1e9;  
    GetAns();

    if(Map[1][1]=='1')putchar('1');
    for(i=1;i<=N+M-Max-1;i++)printf("%d",Ans[i]);

    if(Map[1][1]=='0'&&N+M-Max-1==0)putchar('0');
    //答案就是0,不能输出空串
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值