【0-1BFS】双端队列BFS

【0-1BFS】双端队列BFS

适用范围

可转换为边权值的最短路问题
边权值为有/没有
eg 在走迷宫问题中,你可以花 1 个金币走 5 步,也可以不花金币走 1 步

思路

把没有权值的边扩展到的点放到队首,有权值的边扩展到的点放到队尾
->可保证像普通 BFS 一样整个队列队首到队尾权值单调不下降
image

题目积累

Takahashi the Wall Breaker

https://atcoder.jp/contests/abc400/tasks/abc400_d
注意这题题目表述比较抽象:要看英文
翻译:可以只破坏一面墙,也可以连续破坏两面墙->但是花费相同 都是1
有一点点最短路思想

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
const int N=1010;
int h,w;
string s[N];
int a,b,c,d;
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
bool st[N][N];
int dis[N][N];
bool is_in(int x,int y){
    return x>=0 && x<h && y>=0 && y<w;
}
void solve(){
    cin>>h>>w;
    for(int i=0;i<h;i++){
        cin>>s[i];
    }
    cin>>a>>b>>c>>d;
    a--;b--;c--;d--;
    deque<PII> q;
    memset(dis,-1,sizeof dis);
    dis[a][b]=0;
    q.push_front({a,b});
    while(q.size()){
        PII t=q.front();
        q.pop_front();
        int px=t.first,py=t.second;
        if(st[px][py]) continue;
        st[px][py]=1;
        //1:踢1格/2:踢2格->都是同一种花费:1
        for(int i=0;i<4;i++){
            for(int j=1;j<=2;j++){
                int nx=px+dx[i]*j,ny=py+dy[i]*j;
                if(!is_in(nx,ny)) continue;
                int v=1;
                if(j==1 && s[nx][ny]=='.') v=0;//不需要踢+不需要花费
                if(dis[nx][ny]==-1 || dis[nx][ny]>dis[px][py]+v){
                    dis[nx][ny]=dis[px][py]+v;
                    //【01BFS特点】如果有花费:队尾/无花费:队头
                    if(v){
                        q.push_back({nx,ny});
                    }
                    else{
                        q.push_front({nx,ny});
                    }
                }
            }
        }
    }
    cout<<dis[c][d];
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int T=1;
    //cin>>T;
    while(T--) solve();
    return 0;
}

Chamber of Secrets

https://codeforces.com/problemset/problem/173/B

思路

一个方向射出不需要花费(0),而往四个方向射出需要花费(1)
注意遇到#只改方向 移动在取头来操作

const int N=1010;
const int INF=0x3f3f3f3f;
int n,m;
string s[N];
int dis[N][N][5];//再开一维记录方向
/*
遇到#:是只往一个方向(不花费)还是往4个方向(花费)
要从(0,0)到(n-1,m-1)
*/
struct node{
    int x,y,dir;
};
deque<node> q;
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
/*注意开头和结尾都只能右出!!!dir==0*/
/*【注意起点和终点】
"蛇怪位于网格右下方单元格的右侧,并向左看(在左下方单元格的方向)"->要从蛇怪开始往前找!
人是在0 0点往右看->终点为**反方向**
*/
void solve(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>s[i];
    }
    //初始化成INF:找最短路
    memset(dis,0x3f,sizeof dis);
    deque<node> q;
    q.push_front({n-1,m-1,2});
    dis[n-1][m-1][2]=0;
    while(q.size()){
        node t=q.front();
        q.pop_front();
        int x=t.x,y=t.y,dir=t.dir;
        int d=dis[x][y][dir];
        //cout<<x<<" "<<y<<" "<<dir<<endl;
        //能花费的条件:遇到#->只改方向->还用x y
        if(s[x][y]=='#'){
            for(int i=0;i<4;i++){
                if(i!=dir){
                    if(d+1<dis[x][y][i]){
                        q.push_back({x,y,i});
                        dis[x][y][i]=d+1;
                    }
                }
            }
        }
        //往一个方向走
        int nx=x+dx[dir],ny=y+dy[dir];
        //cout<<nx<<" "<<ny<<endl;
        if(nx<0 || nx>=n || ny<0 || ny>=m) continue;//注意使用了continue就要后放:先改的方向
        //不花费:放前面
        if(d<dis[nx][ny][dir]){
            q.push_front({nx,ny,dir});
            dis[nx][ny][dir]=d;
        }
    }
    if(dis[0][0][2]==INF){
        cout<<"-1"<<endl;
    }
    else cout<<dis[0][0][2]<<endl;
    return;
}

Traffic Lights

https://codeforces.com/contest/2122/problem/D

题目大意

image
deg(u)是节点u的度数

思路

把等候1s看成代价->那就是01BFS无代价插前面 有代价插后面
设dp[i][j]:第i个点在第j分钟最小等候时间
有:

dp[v][j+1]=min(dp[v][j+1],dp[u][j])
dp[u][j+1]=min(dp[u][j+1],dp[u][j]+1)

注意本题最多在2*n的时间内走完->dp第二维可以设时间

代码

const int N=5010;
int n,m;
vector<int> g[N];
void init(int x){
    for(int i=1;i<=x;i++) g[i].clear();
}
/*有/无代价:01bfs:
(1)不走,等候,代价(等候)+1
(2)走,没有代价
*/
void solve(){
    cin>>n>>m;
    init(n);
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    //dp[i][j]:第i个点在第j分钟的最小等候时间
    vector<vector<int>> dp(n+1,vector<int>(2*n+1,inf_int));
    dp[1][0]=0;
    deque<PII> q;
    q.push_front({1,0});
    int ans=inf_int;
    while(q.size()){
        auto [pos,val]=q.front();
        q.pop_front();
        if(val>=ans) continue;
        if(val>2*n) continue;//答案最多不超过2*n步
        if(pos==n) ans=min(ans,val);
        //走
        int len=g[pos].size();
        int v=g[pos][val%len];//这里不用+1:边是0-based
        if(dp[v][val+1]>dp[pos][val]){
            dp[v][val+1]=dp[pos][val];
            q.push_front({v,val+1});
        }
        //不走
        if(dp[pos][val+1]>dp[pos][val]+1){
            dp[pos][val+1]=dp[pos][val]+1;
            q.push_back({pos,val+1});
        }
    }
    cout<<ans<<" "<<dp[n][ans]<<endl;
}
posted @ 2025-04-11 13:38  White_ink  阅读(36)  评论(0)    收藏  举报