模板:双端队列广搜即更新路径的方式不只是通过从一个点的其他种可能加入队尾。也有可能需要加入到队头,例如下题中,操作次数不变情况,继续往队头加,如果边了才往队尾加
(所有题目截图均来自于Acwing)
经典例题:
电路维修:
题目思路:
1)首先确定,如果第一个点的横坐标和纵坐标之和不为偶数,则这个点一定到不了,因为假设当前点能到,他的下一个点可能是'\'或者是'/',第一种情况,横纵坐标+1,第二种情况,横坐标-1,纵坐标+1。当然这是‘\’走的情况,反之情况调转。总之,他到下一个点一定是横纵坐标一起变化1
2)题中所给为图形,假设从0,0点按图形走到n-1,m-1即可。此处考虑情况为从0,0走到终点即n,m。按点走。
3)按照Dikjstra思路,每次找最近点,列举每个点的4种方向,这里举顺时针从左上开始。首先判段走到的点是否越界,随后,将这个点往i方向走的图形对比图中是否相同,不相同则需要操作步数加1。所以这里需要写4个方向的正确图形,然后将这个点在4个方向对应着的原图中下标找出来,对比。
4)如果图形对比相同,则说明不需要操作,加到队列前,不同则步数+1,加到队尾。保证队中dist大小从小到大。为什么加到队前?因为如果现在都加到队尾,无法保证加到队尾的这些情况的顺序。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=510;
int n,m;//行列大小
char g[N][N];//储存图形
int dist[N][N];//到每个点的距离
bool st[N][N];//判段每个点是否走过
int bfs(){
memset(dist,0x3f,sizeof dist);//每次计算先将dist和st初始化
memset(st,0,sizeof st);
dist[0][0]=0;
deque<PII> q;//建一个双端队列,并且将0,0这个点存进去
q.push_back({0,0});
char cs[]="\\/\\/";//从左上开始走的四个方向如果都能到当前点,应该走的什么图形
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1}; //当前点往4个方向走,横纵坐标变化
int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1}; //从当前点,推断4个方向的图形下标
while(q.size()){
PII t=q.front();
q.pop_front();//取出头结点
if(st[t.x][t.y]) continue;
st[t.x][t.y] = true;
for(int i = 0 ; i < 4 ; i++){
//a,b 为 往第i个方向走移动到的下标
int a = t.x + dx[i],b = t.y + dy[i];
//如果越界,则往下一个方向走
if( a<0 || a>n || b<0 || b>m ) continue;
//从当前点,推断往第i个方向走的图形
int ca = t.x + ix[i] , cb = t.y + iy[i];
int d = dist[t.x][t.y] + (g[ca][cb]!= cs[i]);
//如果图形正确,不加值,需要添加一步
if(d < dist[a][b]){
dist[a][b] = d;//如果不正确,添加到队尾,保证队的最前面都是最近的
if(g[ca][cb] != cs[i]) q.push_back({a,b});
else q.push_front({a,b});//正确则添加到队头
}
}
}
return dist[n][m];//最后返回
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
cin>>T;
while(T--){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>g[i];
int t=bfs();
if(t==0x3f3f3f3f) cout<<"NO SOLUTION"<<endl;
else cout<<t<<endl;
}
return 0;
}