多源BFS-双端队列BFS
题目 | 扩展方式 | 类型 |
---|---|---|
矩阵距离 | 多源BFS | BFS最短路 |
魔板 | 扩展顺序,状态图搜索 | 最小步数模型 |
电路维修 | 挖掘性质对角线坐标关系 | 双端队列BFS |
思路
多源BFS,求每个点(遍历到的给定点)到最近起点的距离,这里要区分多源最短路问题(任意两点间的最短距离)
求每个点到最近起点的距离,可以转为单源最短路,建立一个虚拟源点,源点和每个起点连一条边权为0的边。每个点到最近起点的最短距离可以转为每个点到虚拟源点最短距离。因为BFS会自动找距离最短分支,虚拟源点出去的每个分支就是每个起点
另外一道多源BFS:最优配餐
手写队列
int hh=0,tt=0;//下面有直接用了起始下标的才tt=0,实际上等效于下面的
q[0]={sx,sy};
while(hh<=tt}
{
}
int hh=0,tt=-1;
q[++tt]={sx,sy};
while(hh<=tt)
{
}
思路
最小步数模型,BFS逐渐抽象,码量逐渐暴涨,咸鱼逐渐躺下
模型抽象过程
来抽象一个BFS板子
queue<state_type>q;//队列存储状态
unordered_map<state_type,int>dist;
unordered_map<state_type,pair<op_type,state_type>>pre;
//第一个参数以第二个参数.first的方式从第二个参数.second
state_type move0(state_tpye)//转移方式
{
}
bool check(state_type tmp,state_type end)//判当前转移临时结果是否与目标态相等
{
}
op_type get_op_type(int i)//把下标转为操作序列类型
{
}
void bfs(state_type start,state_type end)
{
q.push(start);
dist[start];
while(q.size())
{
auto t=q.front();
q.pop();
state_type move_ans[N];
m[0]=move0(t);
m[1]=move1(t);
……//如果有优先级那么就按优先级映射m数组下标
m[n]=moven(t);
for(int i=0;i<N;i++)//如果有优先级那么就按优先级扩展
{
state_type tmp=m[i];
if(dist.count(tmp)==0)
{
dist[tmp]=dist[t]+1;
pre[tmp]={get_op_type(i),t};
if(check(tmp,end))break;
q.push(tmp);
}
}
}
}
int main()
{
state_type start,end;
//输入初态和目标态
bfs(start,end);
cout<<dist[end]<<endl;
opline_type res;//opline_type 用于存储操作序列的变量类型
while(!check(start,end))
{
res+=pre[end].first;
end=pre[end].second;
}
reverse(res);//想办法反转一下操作序列,如果有优先级(比如按字典序)那建议不要采取反向终点到起点的方式来实现逆序输出,老老实实反转
//输出操作序列
}
操作序列按字典序排,只要扩展的时候按字典序扩展就可以啦
应用上面板子的时候思考
state_type是什么
坐标(PII)
全局地图(string hash)
其他的hash映射搞
op_type是什么
char int
opline_type是什么
数组存,字符串,vector容器存
如何实现优先级
扩展顺序方式,贪心
如何把输入输出转化为相应的类型
代码
#include
using namespace std;
int g[2][4];
unordered_map<string,int>dist;
unordered_map<string,pair<char,string>>pre;
queue<string>q;
void sett(string st)
{
for(int i=0;i<4;i++)g[0][i]=st[i];
for(int i=3,j=4;i>=0;i--,j++)g[1][i]=st[j];
}
string get()
{
string res;
for(int i=0;i<4;i++)res+=g[0][i];
for(int i=3;i>=0;i--)res+=g[1][i];
return res;
}
string move0(string st)//交换上下两行
{
sett(st);
for(int i=0;i<4;i++)swap(g[0][i],g[1][i]);
return get();
}
string move1(string st)//最右一列放到最左边
{
sett(st);
char v0=g[0][3],v1=g[1][3];
for(int i=3;i>0;i--)
for(int j=0;j<2;j++)
g[j][i]=g[j][i-1];
g[0][0]=v0,g[1][0]=v1;
return get();
}
string move2(string st)//4格旋转
{
sett(st);
char v=g[0][1];
g[0][1]=g[1][1];
g[1][1]=g[1][2];
g[1][2]=g[0][2];
g[0][2]=v;
return get();
}
void bfs(string st,string ed)
{
q.push(st);
dist[st]=0;
while(q.size())
{
auto t=q.front();
q.pop();
string m[3];
m[0]=move0(t);
m[1]=move1(t);
m[2]=move2(t);
for(int i=0;i<3;i++)
{
string str=m[i];
if(dist.count(str)==0)
{
dist[str]=dist[t]+1;
pre[str]={char(i+'A'),t};
if(str==ed)break;
q.push(str);
}
}
}
}
int main()
{
int x;
string st,ed;
for(int i=0;i<8;i++)
{
cin>>x;
ed+=char(x+'0');
}
st="12345678";
bfs(st,ed);
cout<<dist[ed]<<endl;
string res;
while(ed!=st)
{
res+=pre[ed].first;
ed=pre[ed].second;
}
reverse(res.begin(),res.end());
if(res.size())cout<<res;
return 0;
}
思路
挖掘性质:只能走到和是偶数的点,和是奇数的点是走不到的。所以不存在既可以主对角线又可以走副对角线的走法
下来相当于边权0或1的无向图求最短路
双端队列,边权0入队头,边权0入对尾。只有当出队时才能确定最小值,而不是一入队就确定了,一个点会多次入队多次被扩展到的。
代码
#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=N*N;
int n,m;
char g[N][N];
int dist[N][N];
bool st[N][N];
int bfs()
{
deque<PII>q;
memset(dist,0x3f,sizeof dist);//每个点可能入队多次所以不要用-1了
memset(st,0,sizeof st);
char cs[5]="\\/\\/";//正确方向
int dx[4]={-1,-1,1,1},dy[4]={-1,1,1,-1};
int ix[4]={-1,-1,0,0},iy[4]={-1,0,0,-1};//根据扩展的点找到对应的电路板坐标
q.push_back({0,0});
dist[0][0]=0;//不要忘记
while(q.size())
{
PII t=q.front();
q.pop_front();
int x=t.x,y=t.y;
if(x==n&&y==m)return dist[x][y];//必须出队的时候才知道最小值
if(st[x][y])continue;
st[x][y]=1;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||b<0||a>n||b>m)continue;
int ga=x+ix[i],gb=y+iy[i];//电路板对应坐标
int w=(g[ga][gb]!=cs[i]);//不通则边权为1,通则边权为0
int d=dist[x][y]+w;
if(d<=dist[a][b])
{
dist[a][b]=d;
if(!w)q.push_front({a,b});
else q.push_back({a,b});
}
}
}
return -1;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%s",g[i]);
if((n+m)&1)puts("NO SOLUTION");
else printf("%d\n",bfs());
}
return 0;
}