[Acwing提高]搜索(多源BFS-双端队列BFS)

[Acwing提高]搜索

多源BFS-双端队列BFS

知识点

题目 扩展方式 类型
矩阵距离 多源BFS BFS最短路
魔板 扩展顺序,状态图搜索 最小步数模型
电路维修 挖掘性质对角线坐标关系 双端队列BFS

题目

矩阵距离

思路
多源BFS,求每个点(遍历到的给定点)到最近起点的距离,这里要区分多源最短路问题(任意两点间的最短距离)
求每个点到最近起点的距离,可以转为单源最短路,建立一个虚拟源点,源点和每个起点连一条边权为0的边。每个点到最近起点的最短距离可以转为每个点到虚拟源点最短距离。因为BFS会自动找距离最短分支,虚拟源点出去的每个分支就是每个起点
另外一道多源BFS:最优配餐
[Acwing提高]搜索(多源BFS-双端队列BFS)_第1张图片

手写队列

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逐渐抽象,码量逐渐暴涨,咸鱼逐渐躺下
模型抽象过程
[Acwing提高]搜索(多源BFS-双端队列BFS)_第2张图片
来抽象一个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;
}

电路维修

思路
挖掘性质:只能走到和是偶数的点,和是奇数的点是走不到的。所以不存在既可以主对角线又可以走副对角线的走法
[Acwing提高]搜索(多源BFS-双端队列BFS)_第3张图片
下来相当于边权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;
}

你可能感兴趣的:(#,搜索,acwing)