【笔记】0-1 bfs

版权声明:欢迎评论交流,转载请注明原作者。 https://blog.csdn.net/m0_37809890/article/details/83109136

简介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且自身连通,现在想要通过修路使三个国家连通,求最少的修路格数。

以每个国家的任意一格为起点,对整张图跑一遍01dfs,就得到了所有格子分别到三个国家的距离(修路数)。遍历所有的格子,最小的到三个国家的距离之和就是答案,注意格子为点号时,答案-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];
}

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/83109136
BFS