简介0-1 bfs
bfs可以O(V+E)求解边权全为1的图上最短路。
而当边权只有0或1时,使用其它最短路算法是有些浪费的,此时可以使用bfs的变种:0-1 bfs来快速求解,复杂度仍为O(V+E).
01bfs维护一个双端队列,当边权为0时,使用push_front,当边权为1时,使用push_back.
节点的出队顺序是这样的:0,0,0,0,0,1,1,1,1,2,2,2,3,3,3,…
01bfs常见于迷宫问题,此外有进阶的(1,0),(1,1)版本(见后文).
uva11573 Ocean Currents
n*n的海面上,每个格子都有一个潮水涌动的方向(共8个方向),初始时船在某个位置,每次可以选择顺着水涌动的方向前进,不消耗能量。或者消耗一个能量前往任一一个方向。问从起始位置到目标位置最少需要消耗多少能量。
直接01bfs即可,一个位置可能会重复入队,但因为松弛,它的孩子不会重复入队,所以可以省略vis数组。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 1024, MOD = 1000000007;
const int go[8][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
char save[M][M];
int dis[M][M],n,m;
int bfs(int str,int stc,int edr,int edc)
{
memset(dis,0x3f,sizeof(dis));
deque<pair<int,int>> bfs;
dis[str][stc] = 0;
bfs.emplace_front(str,stc);
while(!bfs.empty())
{
int r = bfs.front().first, c = bfs.front().second; bfs.pop_front();
if(r==edr&&c==edc) return dis[r][c];
for(int k=0;k<8;++k)
{
int nr = r+go[k][0], nc = c+go[k][1];
if(nr>=1&&nr<=n&&nc>=1&&nc<=m)
{
int nd = dis[r][c]+(k!=save[r][c]-'0');
if(dis[nr][nc] > nd)
{
dis[nr][nc] = nd;
if(nd==dis[r][c]) bfs.emplace_front(nr,nc);
else bfs.emplace_back(nr,nc);
}
}
}
}
return -1;
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
n = read(), m=read();
for(int i=1;i<=n;++i)
scanf("%s",save[i]+1);
int q = read();
while(q--)
{
int r1=read(),c1=read(),r2=read(),c2=read();
printf("%d\n",bfs(r1,c1,r2,c2));
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
Codeforces 590C Three States
n*m的地图上有5种格子:1表示国家一,2表示国家二,3表示国家三,.(点号)表示可以修路的地方,#表示不能修路的地方。保证每个国家的面积至少为1且自身连通,现在想要通过修路使三个国家连通,求最少的修路格数。
以每个国家的任意一格为起点,对整张图跑一遍01bfs,就得到了所有格子分别到三个国家的距离(修路数)。遍历所有的格子,最小的到三个国家的距离之和就是答案,注意格子为点号时,答案-2,因为这个格子只需要修一次。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 1024, MOD = 1000000007;
const int go[4][2] = {{0,1},{0,-1},{-1,0},{1,0}};
char save[M][M];
int dis[4][M][M];
void bfs(int st,int r0,int c0)
{
deque<pair<int,int>> dq;
dq.emplace_front(r0,c0);
dis[st][r0][c0] = 0;
while(!dq.empty())
{
int r = dq.front().first, c = dq.front().second; dq.pop_front();
for(int k=0;k<4;++k)
{
int nr = r+go[k][0], nc=c+go[k][1];
if(save[nr][nc] && save[nr][nc]!='#')
{
int nd = dis[st][r][c] + (save[nr][nc]=='.');
if(nd<dis[st][nr][nc])
{
dis[st][nr][nc] = nd;
if(nd==dis[st][r][c]) dq.emplace_front(nr,nc);
else dq.emplace_back(nr,nc);
}
}
}
}
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n = read(), m=read();
for(int i=1;i<=n;++i)
scanf("%s",save[i]+1);
memset(dis,0x3f,sizeof(dis));
for(int st=1;st<=3;++st)
{
int r0,c0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(save[i][j]==st+'0')
{
r0=i, c0=j;
i=n+1, j=m+1;
}
bfs(st,r0,c0);
}
int ans = -1;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(dis[1][i][j]!=0x3f3f3f3f && dis[2][i][j]!=0x3f3f3f3f && dis[3][i][j]!=0x3f3f3f3f)
{
int val = dis[1][i][j] + dis[2][i][j] + dis[3][i][j] - 2*(save[i][j]=='.');
if(ans==-1||ans>val)
ans = val;
}
}
}
printf("%d\n",ans );
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
GYM100625(BAPC2013) J Jailbreak
n*m的地图表示一个监狱,*表示墙壁,#表示门,$表示犯人,.(点)表示空地,监狱的外部视为连通的空地,最多有两个犯人。问最少打开几扇门可以使得两个犯人以及监狱外部连通。
和上题类似,把外部也看成一个独立的目标即可。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 104, MOD = 1000000007;
const int go[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
int n, m, dis[3][M][M];
char mp[M][M]; //地图
void ZeroOneBfs(int st,int r0,int c0)
{
deque<pair<int,int>> dq;
dq.emplace_front(r0,c0);
dis[st][r0][c0] = 0;
while(!dq.empty())
{
int r = dq.front().first, c = dq.front().second; dq.pop_front();
for(int k=0;k<4;++k)
{
int nr = r+go[k][0], nc = c+go[k][1];
if(nr>=0&&nr<=n+1&&nc>=0&&nc<=m+1&&mp[nr][nc]!='*')
{
int nd = dis[st][r][c] + (mp[nr][nc]=='#');
if(nd<dis[st][nr][nc])
{
dis[st][nr][nc] = nd;
if(mp[nr][nc]=='#') dq.emplace_back(nr,nc);
else dq.emplace_front(nr,nc);
}
}
}
}
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int t = read();
while(t--)
{
memset(dis,0x3f,sizeof(dis));
memset(mp,0,sizeof(mp));
n = read(), m = read();
for(int i=1;i<=n;++i)
scanf("%s",mp[i]+1);
vector<pair<int,int>>start{{0,0}};
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(mp[i][j]=='$')
start.emplace_back(i,j);
for(int kase = 0;kase<3;++kase)
ZeroOneBfs(kase,start[kase].first,start[kase].second);
int ans = INT_MAX;
for(int i=0;i<=n+1;++i)
for(int j=0;j<=m+1;++j)
if(dis[0][i][j]!=0x3f3f3f3f&&dis[1][i][j]!=0x3f3f3f3f&&dis[2][i][j]!=0x3f3f3f3f)
{
int val = dis[0][i][j]+dis[1][i][j]+dis[2][i][j]-2*(mp[i][j]=='#');
ans = min(ans,val);
}
printf("%d\n",ans );
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
Codeforces 1063B Labyrinth
n*m的网格,.(点)表示空地,*表示墙,初始时在某个空地上。最多向左x次,向右y次,上下不限,问有多少个格子是可达的。
记上下的代价为0,左右的代价为1,用01bfs即可。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 2048, MOD = 1000000007;
int n,m,sx,sy,l0,r0;
struct Node
{
Node(int _x,int _y,int _l,int _r):x(_x),y(_y),l(_l),r(_r){}
int x,y,l,r;
};
deque<Node> dq;
char mp[M][M], vis[M][M];
inline void go(int x,int y,int l,int r,int t)
{
if(mp[x][y]=='.' && vis[x][y]==0 && l<=l0 && r<=r0)
{
vis[x][y] = 1;
if(t) dq.emplace_back(x,y,l,r);
else dq.emplace_front(x,y,l,r);
}
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&l0,&r0);
for(int i=1;i<=n;++i)
scanf("%s",mp[i]+1);
vis[sx][sy] = 1;
dq.emplace_front(sx,sy,0,0);
int ans = 0;
for(Node p=dq.front();!dq.empty();++ans)
{
p = dq.front(); dq.pop_front();
go(p.x+1,p.y,p.l,p.r,0);
go(p.x-1,p.y,p.l,p.r,0);
go(p.x,p.y+1,p.l,p.r+1,1);
go(p.x,p.y-1,p.l+1,p.r,1);
}
printf("%d\n",ans );
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
进阶(有疑惑)
除了求边权为0与1的最短路之外,还可以求边权为(1,1)与(1,0)的最短路,二维费用。第一要求是第一维费用最小,第二要求是第二维费用最小。
写法是更新第一维时,插入到队尾。不更新第一维但更新第二维时,插入到队首。
不太懂为什么,系统地学习了图论之后再回来看看。
LA6011 Error
有m种双向飞机航线可以订,此外已经有了n张任意时间的单向飞机票在手里。给定起点和终点,在飞行次数最少的情况下,问最少需要额外订几张票可以从起点飞到终点。
标准的(1,0),(1,1)花费,使用01bfs。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 1000016, MOD = 1000000007;
/*ChainForwardStar*/
int fst[M],eds;
struct Edge
{
int nxt,pnt,wei;
}edg[M];
void addEdge(int a,int b,int w)
{
edg[++eds].pnt = b;
edg[eds].wei = w;
edg[eds].nxt = fst[a];
fst[a] = eds;
}
/*DiscretizationIndex*/
inline int discer(char*str)
{
return (str[0]-'A')*26*26+(str[1]-'A')*26+str[2]-'A';
}
/*ZeroOneBfs*/
int dis0[M],dis1[M]; //优先dis0,然后dis1,答案为dis1
int bfs(int st,int ed)
{
memset(dis0,0x3f,sizeof(dis0));
memset(dis1,0,sizeof(dis1));
deque<int> dq;
dq.push_front(st);
dis0[st] = 0;
while(!dq.empty())
{
int u = dq.front(); dq.pop_front();
for(int ve=fst[u];ve;ve=edg[ve].nxt)
{
int v = edg[ve].pnt;
if(dis0[v]>dis0[u]+1)
{
dis0[v] = dis0[u] + 1;
dis1[v] = dis1[u] + edg[ve].wei;
dq.push_back(v);
}
else if(dis0[v]==dis0[u]+1 && dis1[v]>dis1[u]+edg[ve].wei)
{
dis1[v] = dis1[u] + edg[ve].wei;
dq.push_front(v);
}
}
}
return dis1[ed];
}
int main(void)
{
int t = read();
while(t--)
{
//建图
memset(fst,0,sizeof(fst));
memset(edg,0,sizeof(edg));
eds = 0;
int m = read();
char a[4],b[4];
while(m--)
{
scanf("%s %s",a,b);
addEdge(discer(a),discer(b),1);
addEdge(discer(b),discer(a),1);
}
int n = read(),st,ed;
scanf("%s",a);
st = discer(a);
while(n--)
{
scanf("%s",b);
addEdge(discer(a),discer(b),0);
strcpy(a,b);
}
ed = discer(b);
printf("%d\n",bfs(st,ed));
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
Codeforces 1065D. Three Pieces
题意及解法参见【补题】Codeforces 1065: Educational Round 52 (Rated for Div. 2) ABCD.
这个题建图后使用floyd求距离的复杂度是O(n^6)的,但是边权满足(1,0),(1,1),如果使用01bfs,可以优化到O(n^4).
板子
/*ZeroOneBfs*/
int dis0[M],dis1[M]; //优先dis0,然后dis1,答案为dis1
int bfs(int st,int ed)
{
memset(dis0,0x3f,sizeof(dis0));
memset(dis1,0,sizeof(dis1));
deque<int> dq;
dq.push_front(st);
dis0[st] = 0;
while(!dq.empty())
{
int u = dq.front(); dq.pop_front();
for(int ve=fst[u];ve;ve=edg[ve].nxt)
{
int v = edg[ve].pnt;
if(dis0[v]>dis0[u]+1)
{
dis0[v] = dis0[u] + 1;
dis1[v] = dis1[u] + edg[ve].wei;
dq.push_back(v);
}
else if(dis0[v]==dis0[u]+1 && dis1[v]>dis1[u]+edg[ve].wei)
{
dis1[v] = dis1[u] + edg[ve].wei;
dq.push_front(v);
}
}
}
return dis1[ed];
}