关于BFS和dijkstra(2019.04.20)
我的BFS板子
struct node{/*略*/};//表示一个状态
std::map<node,bool>vis;//判断每个状态是否已访问过
std::queue<node>q;//BFS扩展队列
//BFS主代码
q.push(start_node); //初始节点入队
vis[start_node] = 1;
while (!q.empty())
{
node head = q.front(); //去队首元素进行拓展
q.pop();
for (int i : directions) //遍历每一种拓展方式
{
node new = move(head, i); //求新的状态
if (!valid(new) || vis[new])
continue; //如果新状态不合要求,即出界(不合逻辑)或已访问过
//做题
q.push(new); //新节点入队
vis[new] = 1;
}
}
以上的代码中有很多地方使用了伪代码,或者说,不是我们实际编写的形式。下面给出一个具体实现:
struct node
{
int x, y; //坐标
int step; //到此的步数
node(int x, int y, int step) : x(x), y(y), step(step) {} //构造函数
};
int n, m; //迷宫大小
bool vis[max_n][max_m]; //标记是否已访问
bool can_walk[max_n][max_m]; //标记是否可走
const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; //四个方向
std::queue<node> q; //BFS队列
bool pan(int x, int y) //判断(x,y)是否合法
{
return x >= 1 && x <= n && y >= 1 && y <= m && can_walk[x][y];
}
int bfs(int sx, int sy, int tx, int ty) //从(sx,sy)出发到(tx,ty),返回最少步数,无解则为-1
{
if (!can_walk[sx][sy]) //如果一开始就在墙里,无解
return -1;
q.push(node(sx, sy, 0)); //初始位置入队
vis[sx][sy] = 1;
while (!q.empty())
{
node head = q.front(); //取队首元素
q.pop();
if (head.x == tx && head.y == ty) //如果到达终点
return head.step;
for (int i = 0; i < 4; i++)
{
int xx = head.x + dx[i], yy = head.y + dy[i]; //尝试向四个方向移动
if (!pan(xx, yy) || vis[xx][yy])
continue;
q.push(node(xx, yy, head.step + 1)); //新节点入队
vis[xx][yy] = 1;
}
}
return -1; //如果走过所有可行解点了,仍然没到达终点,无解
}
用BFS的思想理解dijkstra
water_lift这个垃圾至今不会SPFA
这里指的是堆优化的版本。
首先起点入队。每次在队列中取最短路长度最小的,对所有从该点出发出边到达的点进行松弛并加入队列。这就是堆优化版dijkstra的算法轮廓。
下面是一个大量使用了STL的dijkstra,很慢,但是能A掉P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
#define inf INT_MAX
using namespace std;
int n,m,s;
struct edge{
int to,dis;
edge(int a,int b):to(a),dis(b){}
};
vector<edge>edges;
vector<int>G[10003];
void add_edge(int u,int v,int w){
edges.push_back(edge(v,w));
G[u].push_back(edges.size()-1);
}
struct node{
int p,dis;
node(int a,int b):p(a),dis(b){}
};
bool operator< (node a,node b){
return a.dis>b.dis;
}
priority_queue<node>q;
int d[10003];
bool u[10003];
int main(){
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
}
fill(d+1,d+n+1,inf);
d[s]=0;
q.push(node(s,0));
while(!q.empty()){
node x=q.top();
q.pop();
//cout<<x.p<<' '<<x.dis<<endl;
if(u[x.p])continue;
u[x.p]=1;
for(int i=0;i<G[x.p].size();i++){
edge e=edges[G[x.p][i]];
//cout<<'\t'<<e.to<<' '<<d[e.to]<<"->";
d[e.to]=min(d[e.to],d[x.p]+e.dis);
//cout<<d[e.to]<<endl;
q.push(node(e.to,d[e.to]));
}
}
for(int i=1;i<=n;i++){
cout<<d[i]<<' ';
}
}
U64942 【常数PK】#2 单源最短路
在此给出一个神奇的题目,用以测试算法优越性。具体来说,就是按照运行时间得分。具体请见题目内描述。
下面是目前得分最高(9525分)的代码(by __Seniorious__):
// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cctype>
#include<cstdio>
#include<queue>
const int BUF=8000000;
char Buf[BUF],*buf=Buf;
struct edge{
int nxt,to,val;
};
edge t[700010];
int fis[200010],n,m,s,cnt,di[200010],s_,e_,c_;
bool is_vis[200010];
inline void add(int _s,int _e,int _c){
t[++cnt].nxt=fis[s_],fis[s_]=cnt,t[cnt].to=e_,t[cnt].val=c_;
}
struct node{
int dis,num;
inline bool operator<(const node &p)const{
return dis>p.dis;
}
};
std::priority_queue<node,std::vector<node> >h;
inline void read(int &x){
for(x=0;!std::isdigit(*buf);++buf);
for(;std::isdigit(*buf);x=x*10+*buf-'0',++buf);
}
int main(){
//freopen("tst","r",stdin);
fread(buf,1,BUF,stdin);
read(n),read(m),read(s);
for(int i=1;i<=m;++i){
read(s_),read(e_),read(c_);
add(s_,e_,c_);
}
for(int i=1;i<=n;++i)di[i]=2147483647;
di[s]=0;
h.push(node{0,s});
while(!h.empty()){
node QwQ=h.top();h.pop();
int qwq=QwQ.num;
if(is_vis[qwq])continue;
is_vis[qwq]=1;
for(int j=fis[qwq];j;j=t[j].nxt){
int f=t[j].to;
if(t[j].val+di[qwq]<di[f]){
di[f]=t[j].val+di[qwq];
h.push(node{di[f],f});
}
}
}
for(int i=1;i<=n;++i)printf("%d ",di[i]);
printf("\n");
return 0;
}
一些题解
P1451 求细胞数量
算法:BFS
从每个非0格BFS,把遇到的所有非0格都变为0。
#include <bits/stdc++.h>
using namespace std;
int m, n;
char mat[101][101];
struct node
{
int x, y;
node(int x, int y) : x(x), y(y) {}
};
const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
bool pan(int x, int y)
{
return x >= 1 && x <= m && y >= 1 && y <= n;
}
int ans;
int main()
{
cin >> m >> n;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
cin>>mat[i][j];
}
}
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (mat[i][j] == '0')
continue;
ans++;
queue<node> q;
q.push(node(i, j));
mat[i][j] = '0';
while (!q.empty())
{
node h = q.front();
q.pop();
for (int k = 0; k < 4; k++)
{
int xx = h.x + dx[k], yy = h.y + dy[k];
if (!pan(xx, yy) || mat[xx][yy] == '0')
continue;
q.push(node(xx, yy));
mat[xx][yy] = '0';
}
}
}
}
cout << ans;
}
P1443 马的遍历
算法:BFS
从(1, 1)
开始BFS,第一次到达某个点的步数就是最少步数。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pos;
int n, m, x, y;
const int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1}, dy[8] = {2, 1, -1, -2, -2, -1, 1, 2};
inline bool ck(int x, int y)
{
return x >= 1 && x <= n && y >= 1 && y <= m;
}
queue<pos> q;
int d[401][401];
int main()
{
cin >> n >> m >> x >> y;
pos h = pos(x, y);
q.push(h);
memset(d, -1, sizeof(d));
d[x][y] = 0;
while (!q.empty())
{
pos i = q.front();
q.pop();
for (int r = 0; r < 8; r++)
{
int xx = i.first + dx[r], yy = i.second + dy[r];
if (ck(xx, yy) && d[xx][yy] == -1)
{
q.push(pos(xx, yy));
d[xx][yy] = d[i.first][i.second] + 1;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cout << left << setw(5) << d[i][j];
}
cout << endl;
}
}
P1162 填涂颜色
算法:DFS, BFS,flood fill
有点毒瘤。从边界往里推,如果是空白就删掉,剩下的就是图形。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pos;
int n;
int in[31][31], co[31][31];
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
queue<pos> q;
bool ck(int x, int y)
{
return x >= 1 && x <= n && y >= 1 && y <= n;
}
void bfs(int x, int y)
{
pos i = pos(x, y);
q.push(i);
while (!q.empty())
{
pos p = q.front();
q.pop();
co[p.first][p.second] = 1;
for (int d = 0; d < 4; d++)
{
int xx = p.first + dx[d], yy = p.second + dy[d];
if (ck(xx, yy) && in[xx][yy] == 0 && co[xx][yy] == 0)
{
q.push(pos(xx, yy));
}
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> in[i][j];
}
}
for (int i = 1; i <= n - 1; i++)
{
if (in[1][i] == 0 && co[1][i] == 0)
bfs(1, i);
if (in[i][n] == 0 && co[i][n] == 0)
bfs(i, n);
if (in[n][n - i + 1] == 0 && co[n][n - i + 1] == 0)
bfs(n, n - i + 1);
if (in[n - i + 1][1] == 0 && co[n - i + 1][1] == 0)
bfs(n - i + 1, 1);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (in[i][j] == 0 && co[i][j] == 0)
cout << 2;
else
cout << in[i][j];
cout << ' ';
}
cout << endl;
}
}
P1506 拯救oibh总部
算法:DFS, BFS,flood fill
思路和上题一致。从边界往里推,遇到空白格子就删掉,最后数没被删掉的空白格子。
#include <bits/stdc++.h>
using namespace std;
int n, m;
bool wall[501][501];
bool vis[501][501];
const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
struct node
{
int x, y;
node(int x, int y) : x(x), y(y) {}
};
bool valid(int x, int y)
{
return x >= 1 && x <= n & y >= 1 && y <= m && !wall[x][y] && !vis[x][y];
}
void bfs(node s)
{
queue<node> q;
q.push(s);
vis[s.x][s.y] = 1;
while (!q.empty())
{
node head = q.front();
q.pop();
for (int i = 0; i < 4; i++)
{
int xx = head.x + dx[i], yy = head.y + dy[i];
if (!valid(xx, yy))
continue;
q.push(node(xx, yy));
vis[xx][yy] = 1;
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
char c;
cin >> c;
if (c == '0')
wall[i][j] = 0;
else
wall[i][j] = 1;
}
}
for (int i = 1; i <= m - 1; i++)
{
if (valid(1, i))
bfs(node(1, i));
if (valid(n, m - i + 1))
bfs(node(n, m - i + 1));
}
for (int i = 1; i <= n - 1; i++)
{
if (valid(i, m))
bfs(node(i, m));
if (valid(n - i + 1, 1))
bfs(node(n - i + 1, 1));
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (valid(i, j))
ans++;
}
}
cout << ans << endl;
}
P1144 最短路计数
算法:dijkstra
跑dijkstra即可。关于计数,就是如果新方案和原方案的长度一样,那么就累加。累加时注意不能只加一,要把上一个节点的值都加上(不理解的可以看代码)。
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int nmax = 1000001, mmax = 2000001, mod = 100003;
int fir[nmax], to[mmax], nxt[mmax], ecnt;
void add_edge(int u, int v)
{
to[++ecnt] = v;
nxt[ecnt] = fir[u];
fir[u] = ecnt;
}
bool vis[nmax];
int dis[nmax];
int cnt[nmax];
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
add_edge(x, y);
add_edge(y, x);
}
memset(dis, -1, sizeof(dis));
queue<int> q;
cnt[1] = 1;
dis[1] = 0;
q.push(1);
while (!q.empty())
{
int h = q.front();
q.pop();
if (vis[h])
continue;
vis[h] = 1;
for (int e = fir[h]; e; e = nxt[e])
{
if (vis[to[e]])
continue;
if (dis[to[e]] == -1)
{
dis[to[e]] = dis[h] + 1;
cnt[to[e]] = cnt[h] % mod;
}
else
{
if (dis[h] + 1 > dis[to[e]])
continue;
else if (dis[h] + 1 == dis[to[e]])
cnt[to[e]] = (cnt[to[e]] + cnt[h]) % mod;
else
dis[to[e]] = dis[h] + 1, cnt[to[e]]++;
}
q.push(to[e]);
}
}
for (int i = 1; i <= n; i++)
{
cout << cnt[i] << '\n';
}
}
U68862 _GC滑迷宫(其实数据还是很蒻
算法:BFS
仍按迷宫问题思路即可,注意拓展时要while到墙。
#include <bits/stdc++.h>
using namespace std;
int n, m, sx, sy;
bool mat[501][501];
const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
struct pos
{
int x, y, step;
pos(int x, int y, int step) : x(x), y(y), step(step) {}
};
queue<pos> q;
bool vis[501][501];
inline bool pan(int x, int y)
{
return x >= 1 && x <= n && y >= 1 && y <= m && mat[x][y] == 0;
}
bool out(int x, int y)
{
return (x == 1 || x == n || y == 1 || y == m) && mat[x][y] == 0;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> mat[i][j];
}
}
cin >> sx >> sy;
if(mat[sx][sy]){
cout<<-1;
return 0;
}
if(out(sx,sy)){
cout<<0;
return 0;
}
q.push(pos(sx, sy, 0));
vis[sx][sy] = 1;
while (!q.empty())
{
pos h = q.front();
q.pop();
// cout << h.x << ' ' << h.y << endl;
if (out(h.x, h.y))
{
cout << h.step - 1;
return 0;
}
for (int i = 0; i < 4; i++)
{
int xx = h.x, yy = h.y;
while (pan(xx + dx[i], yy + dy[i]))
{
xx += dx[i];
yy += dy[i];
}
if ((xx == h.x && yy == h.y) || vis[xx][yy])
continue;
vis[xx][yy] = 1;
q.push(pos(xx, yy, h.step + 1));
}
}
cout << -1;
}
UVA532 Dungeon Master
算法:BFS
三维迷宫。
#include <bits/stdc++.h>
using namespace std;
int l, n, m;
struct pos
{
int z, x, y, step;
pos(int z, int x, int y, int step) : z(z), x(x), y(y), step(step) {}
};
int sz, sx, sy;
int tz, tx, ty;
bool mat[31][31][31];
bool vis[31][31][31];
const int dz[6] = {1, 0, 0, -1, 0, 0},
dx[6] = {0, 1, 0, 0, -1, 0},
dy[6] = {0, 0, 1, 0, 0, -1};
bool pan(int z, int x, int y)
{
return z >= 1 && z <= l && x >= 1 && x <= n && y >= 1 && y <= m && mat[z][x][y] == 0;
}
int main()
{
while (true)
{
cin >> l >> n >> m;
if (l == 0 && n == 0 && m == 0)
break;
for (int k = 1; k <= l; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
char c;
cin >> c;
if (c == '.')
mat[k][i][j] = 0;
else if (c == '#')
mat[k][i][j] = 1;
else if (c == 'S')
{
mat[k][i][j] = 0;
sz = k;
sx = i;
sy = j;
}
else if (c == 'E')
{
mat[k][i][j] = 0;
tz = k;
tx = i;
ty = j;
}
else
{
cin >> c;
}
vis[k][i][j] = 0;
}
}
}
queue<pos> q;
q.push(pos(sz, sx, sy, 0));
vis[sz][sx][sy] = 1;
bool flag = 1;
while (!q.empty())
{
pos head = q.front();
q.pop();
if (head.z == tz && head.x == tx && head.y == ty)
{
cout << "Escaped in " << head.step << " minute(s).\n";
flag = 0;
break;
}
for (int i = 0; i < 6; i++)
{
int zz = head.z + dz[i], xx = head.x + dx[i], yy = head.y + dy[i];
if (!pan(zz, xx, yy))
continue;
if (vis[zz][xx][yy])
continue;
q.push(pos(zz, xx, yy, head.step + 1));
vis[zz][xx][yy] = 1;
}
}
if (flag)
cout << "Trapped!\n";
}
}
P1379 八数码难题
算法:BFS
从初始状态开始,做BFS即可,每次转移时枚举空白格子的四个方向。
#include <bits/stdc++.h>
using namespace std;
int n;
const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
queue<int> q;
map<int, int> d;
bool valid(int x, int y)
{
return x >= 0 && x <= 2 && y >= 0 && y <= 2;
}
int main()
{
cin >> n;
q.push(n);
d[n] = 0;
while (!q.empty())
{
int head = q.front();
q.pop();
if (head == 123804765)
break;
int state[3][3], x, y;
int f = head;
for (int i = 2; i >= 0; i--)
{
for (int j = 2; j >= 0; j--)
{
state[i][j] = f % 10;
f /= 10;
if (!state[i][j])
x = i, y = j;
}
}
for (int i = 0; i < 4; i++)
{
int xx = x + dx[i], yy = y + dy[i];
int st = 0;
if (!valid(xx, yy))
continue;
swap(state[x][y], state[xx][yy]);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
st = st * 10 + state[i][j];
}
}
if (!d.count(st))
{
d[st] = d[head] + 1;
q.push(st);
}
swap(state[x][y], state[xx][yy]);
}
}
cout << d[123804765] << endl;
}
UVA10603 倒水问题 Fill
算法:BFS or dijkstra
设水杯内水量为状态,倒水为转移,维护答案即可。当然也可以认为是dijkstra。
#include <bits/stdc++.h>
using namespace std;
int t;
struct node
{
int v[3], dis;
node(int a, int b, int c, int d)
{
v[0] = a;
v[1] = b;
v[2] = c;
dis = d;
}
};
bool operator<(node x, node y)
{
return x.dis > y.dis;
}
int c[3], d;
bool vis[201][201];
int dis[201][201];
int ans[201];
int main()
{
cin >> t;
while (t--)
{
cin >> c[0] >> c[1] >> c[2] >> d;
memset(vis, 0, sizeof(vis));
memset(ans, -1, sizeof(ans));
memset(dis, -1, sizeof(dis));
priority_queue<node> q;
q.push(node(0, 0, c[2], 0));
dis[0][0] = 0;
vis[0][0] = 1;
while (!q.empty())
{
node head = q.top();
q.pop();
// if(vis[head.v[0]][head.v[1]])continue;
// vis[head.v[0]][head.v[1]]=1;
for (int i = 0; i < 3; i++)
{
if (ans[head.v[i]] == -1)
ans[head.v[i]] = head.dis;
else
ans[head.v[i]] = min(ans[head.v[i]], head.dis);
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == j)
continue;
int amount = min(head.v[i], c[j] - head.v[j]);
node x = head;
x.v[i] -= amount;
x.v[j] += amount;
x.dis += amount;
if (vis[x.v[0]][x.v[1]])
continue;
if (dis[x.v[0]][x.v[1]] == -1 || dis[x.v[0]][x.v[1]] > x.dis)
{
dis[x.v[0]][x.v[1]] = x.dis;
vis[x.v[0]][x.v[1]] = 1;
q.push(x);
}
}
}
}
for (; d >= 0; d--)
{
if (ans[d] >= 0)
{
cout << ans[d] << ' ' << d << endl;
break;
}
}
}
}