最短路模型
迷宫问题
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int> pii;
const int N= 1010;
int g[N][N];
int n;
bool st[N][N];
pii pr[N][N];
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
void bfs(int x,int y){
queue<pii> q;
q.push({x,y});
st[x][y]=true;
while(q.size()){
auto t=q.front();
q.pop();
for(int i=0;i<4;i++){
int tx=t.fi+dx[i],ty=t.se+dy[i];
if(tx<0||tx>=n||ty<0||ty>=n) continue;
if(g[tx][ty]==1) continue;
if(st[tx][ty]) continue;
// if(tx==0&&ty==0){
// pr[tx][ty]=t;
// break;
// }这个会过不去,break只是跳出循环,而不是直接退出
q.push({tx,ty});
st[tx][ty]=true;
pr[tx][ty]=t;
}
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>g[i][j];
bfs(n-1,n-1);
pii end={0,0};//end(0,0)
while(!(end.fi==n-1&&end.se==n-1)){
cout<<end.fi<<" "<<end.se<<endl;
// end.fi=pr[end.fi][end.se].fi;
// end.se=pr[end.fi][end.se].se;
end=pr[end.fi][end.se];//上面代码会tle
}
cout<<n-1<<" "<<n-1;
return 0;
}
debug
1.TLE部分——赋值end.fi的同时把end改变了
2.if 语句会卡,过不去。bfs本质是第一次找到的最小路径(距离),如果用break只是跳出for循环,如果后面还有更长路径能到达终点,那么最短路径就会被更新为后面的那条长些的路径。所以第一次找到后就应该跳出while循环,直接退出函数,用return即可。
总结
- PII pair的用法
- 求路径的技巧。起点开始,后一个点通过当前 pr数组 信息来获得,最后到达终点停止(看成一个个点移动)
武士风度的牛
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<vector>
#include<map>
#include<set>
#include<queue>
#define int long long
#define inf 0x3f3f3f3f
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
const int N=200;
char g[N][N];
int dist[N][N];
int n,m;
bool st[N][N];
pii q[N*N];
int dx[8]={-2,-2,-1,1,2,2,1,-1},dy[8]={-1,1,2,2,1,-1,-2,-2};
int bfs(int x,int y){
memset(dist,-1,sizeof dist);
// queue<pii> q;
// q.push({x,y});
int hh=0,tt=0;
q[0]={x,y};
dist[x][y]=0;
st[x][y]=true;
while(hh<=tt){
auto t=q[hh++];
for(int i=0;i<8;i++){
int fx=t.fi+dx[i],fy=t.se+dy[i];
if(fx<0||fx>=n||fy<0||fy>=m) continue;
if(g[fx][fy]=='*') continue;
if(dist[fx][fy]!=-1) continue;
if(st[fx][fy]) continue;
if (g[fx][fy] == 'H') return dist[t.fi][t.se] + 1;//不return会被覆盖
st[fx][fy]=true;
q[++tt]={fx,fy};
dist[fx][fy]=dist[t.fi][t.se]+1;
}
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>m>>n;
int sx,sy,tx,ty;
// for (int i = 0; i < n; i ++ ) cin >> g[i];
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)
{
cin>>g[i][j];
if(g[i][j]=='K'){
sx=i,sy=j;
}
if(g[i][j]=='H')
tx=i,ty=j;
}
}
cout<<bfs(sx,sy);
// cout<<dist[tx][ty];答案错误会被覆盖,是先直接return的,还没有更新dist,所以还是-1
// for(int i=0;i<n;i++){
// for(int j=0;j<m;j++)
// cout<<dist[i][j]<<" ";
// cout<<endl;
// }
return 0;
}
debug
- 没有return,让while循环一直执行到最后,最后的距离不是最短的,会被覆盖掉。
- 返回dist[tx][ty]没有注意细节,因为是先return在更新的,所以终点的dist没有被更新,所以如果输出dist终点的话,会是-1;
- 如果不return,会发生Segmentation Fault 错误,内存超了
- 用st和dist都可以做判断标记
- tx,ty读入的时候逗号写成分号,导致ty不是正确坐标。最后debug半天必须用函数里面的回来,麻了,下次注意,要么就老实写大括号包起来。
总结
- bfs是求最短的性质,所以第一个碰到的点肯定是最短了,就直接return就行,切记不要用break,不然可以只是退出for循环。
- 用dist当标记数组
- 马走法的方法。dx[8]={-2,-2,-1,1,2,2,1,-1},dy[8]={-1,1,2,2,1,-1,-2,-2}
抓住那头牛
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N = 1e5+10;
int n,k;
int dist[N];
int bfs(int x){
if(x==k) return 0;
memset(dist,-1,sizeof dist);
queue<int> q;
q.push(x);
dist[x]=0;
while(q.size()){
auto t=q.front();
q.pop();
if(t==k) return dist[t];
if(dist[t+1]==-1&&t+1<=N){
q.push(t+1);
dist[t+1]=dist[t]+1;
}
if(dist[t-1]==-1&&t-1>=0){
q.push(t-1);
dist[t-1]=dist[t]+1;
}
if(t*2<=N&&dist[2*t]==-1){
q.push(2*t);
dist[t*2]=dist[t]+1;
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>k;
cout<<bfs(n);
return 0;
}
总结
这题写得很顺利,对于搜索,关键在于找到搜索的方向,考虑怎么去扩展节点。
模型归纳
- bfs有最短的性质,一般第一次搜索到的点就是就有最短的性质,(弹出的队头也是)。如果是双向队列的话,就用第一次弹出的队头,因为第一次放进去的队尾不一定是最短的。
- 注意细节,理解标记的作用,体会每次dist是怎么更新的。
多源BFS
矩阵距离
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N = 1100;
int n,m;
char g[N][N];
bool st[N][N];
int dist[N][N];
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
void bfs(){
memset(dist ,-1,sizeof dist );
queue<pii> q;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
if(g[i][j]=='1'){
q.push({i,j});
dist[i][j]=0;
}
}
while(q.size()){
auto t=q.front();
q.pop();
for(int i=0;i<4;i++){
int tx=t.fi+dx[i],ty=t.se+dy[i];
if(tx<0||tx>=n||ty<0||ty>=m) continue;
if(dist[tx][ty]!=-1) continue;
q.push({tx,ty});
dist[tx][ty]=dist[t.fi][t.se]+1;
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++) cin>>g[i];
bfs();
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cout<<dist[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
总结
- 哈曼顿距离的定义了解一下
- 实际是多源点最短路问题,还是利用队列的两段性,将所有源点放进去搜索即可
最小步数模型
模板
#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<string,char>pii;
const int N = 1100;
string s,ed="12345678";
queue<string> q;
map<string,int> dist;
map<string,pii> pre;
string change(string t,int k){
string p;
if(k==0) p={t[7],t[6],t[5],t[4],t[3],t[2],t[1],t[0]};
if(k==1) p={t[3],t[0],t[1],t[2],t[5],t[6],t[7],t[4]};
if(k==2) p={t[0],t[6],t[1],t[3],t[4],t[2],t[5],t[7]};
return p;
}
int bfs(string p){
q.push(p);
dist[p]=0;
while(q.size()){
auto t=q.front();
q.pop();
if(t==s) return dist[t];
for(int i=0;i<3;i++){
auto state=change(t,i);
if(dist.count(state)) continue;
q.push(state);
dist[state]=dist[t]+1;
pre[state]={t,'A'+i};
}
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for(int i=0;i<8;i++){
int x;
cin>>x;
s+=(x+'0');
}
if(s==ed){
cout<<0;
return 0;
}
cout<<bfs(ed)<<endl;//不能bfs(s),因为题意是要从基本状态开始
string t="",p=s;
while(true){
// cout<<p<<" "<<pre[p].fi<<" "<<pre[p].se<<endl;
t+=pre[p].se;
if(pre[p].fi==ed) break;
p=pre[p].fi;
}
reverse(t.begin(),t.end());
cout<<t;
// string t="",p=ed;
// while(true){
// // cout<<pre[p].fi<<" "<<pre[p].se<<endl;
// t+=pre[p].se;
// if(pre[p].fi==s) break;
// p=pre[p].fi;
// }
// // cout<<t<<endl;
return 0;
}
debug
题目没有看清楚,问的是从标准状态开始的字典序最小,所以要用ed开始搜索。最后注意反转输出就行。
总结
这题还是最短路性质,只不过每次扩展方向是改变的全局,然后就是注意输出路经的方法。