OpenJudge 6043 哆啦A梦的时光机——又短又快的双向广搜

题目链接
早上也写了一篇这道题关于双向广搜的题解,但那个写法有一个漏洞,而且很慢,在下面我将一一道来。我们知道,单向广搜时由起始点出发,引出4个分支,再由4个分支引出16个分支,可以看出这个增长速度是非常快的,而且随着搜索层数递增。于是加入双向广搜进行时间优化,双向广搜是由起点和终点两个点出发不断交替引出分支,当两者的分支发生碰撞时就可以得出答案了。重要的是”交替”两个字,不是指两边交替着一个一个引出分支,而是一层一层地引出分支(这里指一个步数一个步数地引出),原因很简单,一个一个引出分支可能会导致答案更大,也就是原先可以以x+1步引出的数,现在要以x+2步引出,显然错误。
另外还有三个优化的方法:1.每次BFS前不需要将step初始化,因为一个数的visit如果更新了,那么它的step也会更新;2.其实visit也不需要初始化,只要每次记下这是第几组数据,然后给对应的正向搜索和反向搜索编上号就行了;3.可以将系统队列转化为自定义的数组,这显然可以加快时间,而且可以只开在一个数组里,只要一个由左往右,一个由右往左就行了。
下面给出代码。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#define M 2000005
using namespace std;
template <class T>
inline void Rd(T &res){
    char c;res=0;int k=1;
    while(c=getchar(),c<48&&c!='-');
    if(c=='-'){k=-1;c='0';}
    do{
        res=(res<<3)+(res<<1)+(c^48);
    }while(c=getchar(),c>=48);
    res*=k;
}
bool found;
int Q[M];
int visit[M];//存节点是否被遍历过,0为正向遍历过,1为反向遍历过,-1为未遍历 
int step0[M];//正向搜索的步数保存 
int step1[M];//反向搜索的步数保存 
int res;//搜索到的共同节点 
bool check(int k,int x){
    if(k==0)return x+1<M;
    if(k==1)return x-1>0;
    if(k==2)return 2*x<M;
    return !(x&1);
}
int f(int k,int x){
    if(k==0)return x+1;
    if(k==1)return x-1;
    if(k==2)return 2*x;
    return x/2;
}
void bfs(int &L,int &R,bool flag,int time){
    int x;
    if(!flag)x=Q[L++];
    else x=Q[R--];
    int step;
    if(!flag)step=step0[x];
    else step=step1[x];
    for(int i=0;i<4;i++)
    if(check(i,x)){
        int nx=f(i,x);
        if(!flag){//在正向队列中判断 
            if(visit[nx]!=time){//未在正向队列中出现
                step0[nx]=step+1;
                if(visit[nx]==time+1){
                    found=true;
                    res=nx;
                    return;     
                }
                visit[nx]=time;
                Q[R++]=nx;
            }
        }else{//在反向队列中判断 
            if(visit[nx]!=time+1){//未在反向队列中出现 
                step1[nx]=step+1;
                if(visit[nx]==time){
                    found=true;
                    res=nx;
                    return;
                }
                visit[nx]=time+1;
                Q[L--]=nx;
            }
        }
    }
}
void BFS(int s,int t,int time){
    found=false;
    visit[s]=time,visit[t]=time+1,step0[s]=0,step1[t]=0;
    int L0=0,R0=0,L1=1999999,R1=1999999;
    Q[R0++]=s;Q[L1--]=t;
    while(L0<R0||L1<R1){
        int R=R0;
        while(L0<R)bfs(L0,R0,0,time);
        if(found)return;
        int L=L1;
        while(L<R1)bfs(L1,R1,1,time);
        if(found)return;
    }
}
int T,s,t;
int main(){
    memset(visit,-1,sizeof(visit));
    Rd(T);
    while(T--){
        Rd(s);Rd(t);BFS(s,t,T<<1);
        printf("%d\n",2*(step0[res]+step1[res]));
    }
    return 0;
}

另外,这里有一个提醒:慎用位运算,因为位运算的优先级有一些古怪,容易造成一些错误,博主就因为这个问题WA了好多次。

此题虐我千百遍,誓待此题如初恋/(ㄒoㄒ)/~~

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值