专题:关于搜索优化——A*和双向广搜

关于搜索的优化

  • 搜索的问题可能在搜索的思路方面很难有(很难想到)什么大的优化,其思路可能和水分的思路是大同小异的。
  • 搜索的限制条件有两个
    • 空间:我们需要通过仔细的挖掘状态的规律,量化状态来压缩内存。
    • 时间:我们可以通过不同的搜索方式和A*的优化来加速

对于不同的搜索方式

  • 我们先来讲讲最简单的两种搜索方式
    • DFS:我们通过深度优先的搜索方式,只有在搜索完了所以的情况以后才会得到最优解,往往时间复杂度大。
    • BFS:我们通过广度优先的搜索方式,一层一层的搜索只要搜索到了解就是最优解,可是要消耗很大的空间。
  • 当我们发现了他们的不足之后,新的搜索方式(新的变形)就出现了。
    • 双向BFS:既然BFS有这样的限制,为什么不从两端都开始搜索呢?由于BFS的特性,两端依次一层一层的扩张开来,他们第一次产生的交点不就是最优解了吗?可惜的是双向广搜在一些没有或者难以实现终点回退逆操作的时候最束手无策了。
    • IDA*:这是DFS的一种变形,由于DFS只有在搜索到最深的时候才会停止搜索。所以我们可以通过限制它的深度来搜索,即每次给它一个深度限制只搜索深度限制内的节点。

对于搜索的A*优化

  • h(x) 估价函数:我们可以用 h(x) 来表示当前状态到达目标状态的最小值(或是一个相对值)*(由于在一些BFS h(x) 只要是正确的我们就可以把它适当的放大把它作为一种相对值 h(x) 相差越大其优化效果往往根明显)。
  • DFS:我们发现DFS在搜索的时候是漫无目的的即使搜索到了一个解也没有什么用,所以我们可以在搜索到了一个解后用 h(x) 和其比较一下,如果 h(x) 都比它大了,就直接停止搜索了。
  • BFS:我们可以通过优先队列来从 h(x) 小的开始搜索。

例题

哆啦A梦

  • 由于正反向的操作完全一样,我们可以很容易的想到用双向广搜。我们就把它当模板吧。
#include<bits/stdc++.h> 
using namespace std;
const int M=1e6;
int cas,D[2][M+5],R[2][M+5],l,r,w,S,T;
struct P{int x,s;bool f;}t,tmp,Q[M*5];
int val(int x,bool f){
    if(R[f][x]!=cas)return -1;
    return D[f][x];
}
void insert(int x,bool f,int c){
    /*由于第一次搜索到的就是最优的,故判掉重复的情况*/ 
    if(R[f][x]==cas)return;
    R[f][x]=cas,D[f][x]=c;
    tmp.x=x;tmp.f=f;tmp.s=c;
    Q[r++]=tmp;
}
void solve(){
    cas++;l=r=0;
    scanf("%d%d",&S,&T);
    insert(S,0,0);insert(T,1,0);
    while(l<r){
        /*取出当前的编号*/
        int F=Q[l].f;
        /*如果编号一样说明是同一边的,我们就开始这一次的搜索*/
        while(F==Q[l].f){
            t=Q[l++];
            int x=t.x,s=t.s;bool f=t.f;
            int c=val(x,!f);
            /*如果发现有了交点就说明找到了答案*/ 
            if(~c){printf("%d\n",2*(c+s));return;}
            s++;
            /*自由的搜索即可*/ 
            if(x!=M)insert(x+1,f,s);
            if(x)insert(x-1,f,s);
            if(x*2<=M)insert(x*2,f,s);
            if(x%2==0)insert(x/2,f,s);
        }
    }
}
int main(){
    scanf("%d",&w);
    while(w--)solve();
    return 0;
}

贪吃蛇

  • 首先我们需要考虑空间复杂度
    如果在k=8时我们需要记录8个点的坐标于是我们需要0.5*8=4个int的大小去存
    可是蛇上每个部分的相对位置可以所有4进制存下
    • 如图
      [1]####
      [2]##[6]
      [3][4][5]
  • 对于这样一条蛇我们可以记录蛇的头坐标
char rx[]={0,1,0,-1};
char ry[]={1,0,-1,0};
void add(char x,char y){
    char i=0;for(;i<4;i++)
    if(x==tx+rx[i]&&y==ty+ry[i])break;
    A<<=2;A+=i;
}
  • 用A记录其相对位置于是每条蛇可以用0.5+0.25*3个int记录,于是内存方面的问题就解决了

用 A * 优化BFS

  • 我们可以用一遍BFS预处理出每个点到达终点的最小距离(不考虑蛇身)。把 h(i)=dis[x][y]+step 作为估价函数。
/*预处理出dis[x][y]*/
bool check(node a,int i){
    a.x-=rx[i];a.y-=ry[i];
    a.a>>=2;a.a+=v[k-2]*i;
    if(a.x<=n&&a.x>=1&&a.y<=m&&a.y>=1&&!s[a.x][a.y])
        if(!mark[a.x][a.y][a.a]){
        Q.push(a);mark[a.x][a.y][a.a]=1;
    }
}
void ck(R a){
    if(a.x<=n&&a.x>0&&a.y<=m&&a.y>0&&!s[a.x][a.y]&&!dis[a.x][a.y]){
        dis[a.x][a.y]=a.s;p[r++]=a;
    }
}
void init(){
    dis[1][1]=1;p[r++]=(R){1,1,0};
    while(l<r){
        R t=p[l++];R v;
        for(int i=0;i<4;i++){
            v.x=t.x+rx[i];v.y=t.y+ry[i];
            v.s=t.s+1;ck(v);
        }
    }
}
void bfs(){
    while(!Q.empty()){
        memcpy(s,S,sizeof(S));
        nw=Q.top();Q.pop();
        tx=nw.x;ty=nw.y;A=nw.a;
        if(nw.step>=100)break;
        if(tx==1&&ty==1){printf("%d\n",nw.step);return;}
        s[tx][ty]=1;
        for(int i=k-2;i>=0;i--){
            tx+=rx[A/v[i]%4];
            ty+=ry[A/v[i]%4];
            s[tx][ty]=1;
        }nw.step+=1;
        for(int i=0;i<4;i++)check(nw,i);
    }puts("-1");
}

用A*优化DFS

  • 同样的我们可以利用估价函数来实现加速
void dfs(char x,char y,char step,short A){
    /*A*优化*/if(dis[x][y]+step>=now_ans)return;
    if(x==1&&y==1){now_ans=step;return;}
    if(mark[tx][ty][A])return;
    mark[tx][ty][A]=1;
    sx=x,sy=y;
    memcpy(s,S,sizeof(S));
    s[sx][sy]=6;
    for(int i=k-2;i>=0;i--){
        sx+=rx[A/v[i]%4];
        sy+=ry[A/v[i]%4];
        s[sx][sy]=6;
    }A/=4;
    for(int i=0;i<4;i++){
        B=A+v[k-2]*i;
        tx=x-rx[i],ty=y-ry[i];
        if(!check(tx,ty))continue;
        if(mark[tx][ty][B])contienu;
        mark[tx][ty][B]=1;
        dfs(tx,ty,step+1,B);
        mark[tx][ty][B]=0;
    }
}

序列还原

双向广搜
  • 很容易想到暴力的BFS算法又发现在这里正逆操作差不多故比较容易可以想到双向广搜
  • 可是由于map太慢了,不加上”排第几”映射只有90分。(性价比比较高了)
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll; 
const int M=20,N=1e5+5;
ll B[M],T,S;
int n,m,x,C[N][2];
struct P{
    ll x;int s;bool f;
}t,s;
queue<P>Q;
map<ll,int>K[2];
int get(ll x,int c){
    c=n-c;
    return(x/B[c])%13;
}
ll Swap(ll x,int a,int b){
    int Ab=get(x,a),Bb=get(x,b);
    x+=(Ab-Bb)*B[n-b];
    x+=(Bb-Ab)*B[n-a];
    return x;
}
int main(){
    scanf("%d %d",&n,&m);
    B[0]=1;For(i,1,13)B[i]=B[i-1]*13;
    For(i,1,n)scanf("%d",&x),T=T*13+i,S=S*13+x;
    for(int i=1;i<=m;i++)
        scanf("%d %d",&C[i][0],&C[i][1]);
    K[0][S]=0;K[1][T]=0;
    Q.push((P){S,0,0});
    Q.push((P){T,0,1});
    while(!Q.empty()){
        bool f=Q.front().f;
        while(f==Q.front().f){
            s=Q.front();Q.pop();
            ll x=s.x,y;
            bool g=s.f;
            int step=s.s;
            if(K[!g].count(x)){
                printf("%d\n",K[!g][x]+step);
                return 0;
            }step++;
            For(i,1,m){
                int a=C[i][0],b=C[i][1];
                y=Swap(x,a,b);
                if(K[g].count(y))continue;
                K[g][y]=step;
                Q.push((P){y,step,g});
            }           
        }
    }
    return 0;
}
A*优化的BFS
  • 我们可以把当前串和结果的不同处来作为 h(x) 的一部分即 h(x)=difference+step 。讲道理,虽然这可以保证正确性却不一定保证最优性,因为当前状态变为最终状态的最小步数 difference+1/2 ,又这里我们为了加强优化效果就不这样处理了,我们可以想当然的认为,前状态变为最终状态的步数和 difference 相差较小。
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=1005;
ll B[M],T,S;
int n,m,ans=2e9,C[M][2];
map<ll,bool>mark;
struct node{
    ll x;int f,s;
    bool operator<(const node&A)const{return A.f<f;}
}tmp,t;
priority_queue<node>Q;
int val(ll x){
    int res=0;
    for(int i=n;i>=1;i--){
        if(x%13!=i)res++;
        x/=13;
    }return res;
}
void insert(ll x,int s){
    if(mark[x])return;
    int f=val(x)+s;
    tmp.f=f;tmp.x=x;tmp.s=s;
    Q.push(tmp);    
}
int get(ll x,int c) {
    c=n-c;
    return(x/B[c])%13;
}
ll Swap(ll x,int a,int b) {
    int Ab=get(x,a),Bb=get(x,b);
    x+=(Ab-Bb)*B[n-b];
    x+=(Bb-Ab)*B[n-a];
    return x;
}
void pt(ll S){
    for(int i=1;i<=n;i++){
        printf("%d ",S%13);
        S/=13;
    }puts("");
}
int main(){
    scanf("%d %d",&n,&m);
    B[0]=1;
    For(i,1,13)B[i]=B[i-1]*13;
    For(i,1,n){
        int x;
        scanf("%d",&x);
        T=T*13+i,S=S*13+x;
    }
    For(i,1,m)
        scanf("%d %d",&C[i][0],&C[i][1]);

    insert(S,0);
    while(!Q.empty()){
        t=Q.top();
        Q.pop();
        ll x=t.x;
        int s=t.s;
        mark[x]=1;
        if(x==T){
            printf("%d",s);
            return 0;
        }s++;
        for(int i=1;i<=m;i++){
            int a=C[i][0],b=C[i][1];
            ll y=Swap(x,a,b);
            insert(y,s);
        }
    }puts("NEMA");
    return 0;
}
A*优化的DFS
  • 我们发现题目给了很多对 a,b 此时我们可以把它看成 a,b,1 这样一条边。通过 Floyd 来得到交换任意两个位置的 cost 。所以对于一些会让总的 cost 增大的操作我们就不进行了。
  • 同时我们的 h(x) 也就处理出来了。
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long ll;
const int M=1005;
int n,m,ans=2e9,A[M],B[M],S[M];
int fa[M],dis[M][M],mp[M];
void Min(int &a,int b){if(a>b)a=b;}
int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
void dfs(int res,int st){
    if((res+1)/2+st>=ans)return;
    if(!res){Min(ans,st);return;}
    For(i,1,m){
        int a=A[i],b=B[i];
        int nxt=res-dis[S[a]][a]-dis[S[b]][b];
        swap(S[a],S[b]);
        nxt+=dis[S[a]][a]+dis[S[b]][b];
        /*如果交换了反而不优了就不交换了*/
        if(nxt>=res){swap(S[a],S[b]);continue;}
        dfs(nxt,st+1);
        swap(S[a],S[b]);
    }   
}
int main(){
    memset(dis,63,sizeof(dis));
    scanf("%d %d",&n,&m);
    ans=2e9;
    For(i,1,n)scanf("%d",&S[i]),fa[i]=i,mp[S[i]]=i;
    For(i,1,m){
        scanf("%d %d",&A[i],&B[i]);
        fa[find(A[i])]=find(B[i]);
        dis[A[i]][B[i]]=dis[B[i]][A[i]]=1;
    }
    /*弗洛伊德跑一边即可*/ 
    For(i,1,n)dis[i][i]=0;
    For(k,1,n)For(i,1,n)For(j,1,n)Min(dis[i][j],dis[i][k]+dis[k][j]);
    For(i,1,n)For(j,1,n)if(dis[i][j]>1)++dis[i][j];
    int res=0;
    /*最初的h(x)*/ 
    For(i,1,n){
        res+=dis[i][mp[i]];
        if(find(mp[i])!=find(i)){puts("NEMA");return 0;}
    }dfs(res,0);
    printf("%d\n",ans);
    return 0;
}
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值