双端队列BFS:电路维修 解决边权是0,1的问题

在这里插入图片描述
一个重要性质:横纵坐标和为奇数的点,永远走不到反转不会影响这个性质。

dx,dy确定点的坐标,ix,iy确定线的坐标

在读入数据的时候,我们输入的是线,但是我们的BFS是按照点来做的

所以向四个方向扩展时,每一个点要找对应4个方向的线, 看看走到对角线上点的位置需要0还是1

每一步的长度为0或1,1表示需要反转一下

在这里插入图片描述
真正的BFS:第一次遍历到的时候就是最小值了。
真正的双端队列:第一次遍历的时候不一定是最小值,但是第一次出队的时候一定是最小值了。

这道题目可以看成是特殊的dijkstra算法,在dijkstra算法中,
某些点可能会被多次扩展,但第一次从优先队列中弹出的节点的
距离一定就是最小值了,所以需要在出队的时候来判重

由于图中没有边权为负数的点, 所以dijkstra一定可以做. 又由于图中的边权只有0和1, 所以堆优化版的dijkstra中的堆,就可以用双端队列来实现.

图源:https://www.acwing.com/solution/content/21775/

#include<cstring>
#include<iostream>
#include<algorithm>
#include<deque>//双端队列

#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];//注意起点是从0,0开始的
bool st[N][N];

int bfs()
{
    //由于有多组输入,所以每次都要初始化
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[0][0]=0;
    deque<PII> q;
    q.push_back({0, 0});
    
    char cs[]="\\/\\/";// 表示某个点可选的走到4个点的花费为0字符,\/\/,顺时针  /的话需要转义字符
    int dx[]={-1, -1, 1, 1}, dy[]={-1, 1, 1, -1};//表示当前交叉点可以走到的四个点的坐标 左上  左下 右下  右上
    // 当前交叉点要走到上边的那四个点,在g数组中,对应的位置
    // 假设当前点的坐标是x,y. 则走到x-1, y-1,在g数组中对应的导线就存在g[x-1][y-1];想要走到x-1,y+1,对应的导线存在g[x-1][y];想要走到x+1, y+1, 对应的导线存在g[x][y]...... 
    int ix[]={-1, -1, 0, 0}, iy[]={-1, 0, 0, -1};
    
    while(q.size())
    {
        PII t=q.front();
        q.pop_front();//删掉对头
        
        //所有边的权重不同,所以某些点可能需要被扩展多次。
        // 这道题目可以看成是特殊的dijkstra算法,在dijkstra算法中,
        // 某些点可能会被多次扩展,但第一次从优先队列中弹出的节点的
        // 距离一定就是最小值了,所以需要在出队的时候来判重
        if(st[t.x][t.y]) continue;//扩展过了,就直接跳过,
        
        st[t.x][t.y]=true;
        
        for(int i=0; i<4; i++)
        {
        	// 对应交叉点
            int a=t.x+dx[i], b=t.y+dy[i];
            if(a<0 || a>n || b<0 || b>m) continue;
            
            //判断g中四个方向是什么字符,即看一下走到对角线花费是1还是0
            int ca=t.x+ix[i], cb=t.y+iy[i];
            //看看像走到的方向与g中给出的导线方向是否相同,与理论方向相同,则加0,不同则加1
            int d=dist[t.x][t.y]+(g[ca][cb] != cs[i]);
            
            //每个点可能会被扩展多次,第一次被扩展到的时候不一定是最优解。
            //有可能a,b由第一个节点扩展而来,但是距离加了1,但是从第二个节点扩展而来就加0,
            //所以第二次扩展的d竟然小于第一次扩展而来的d
            // 只有这个点第一次从堆中出来的时候,也就是当前点是当前堆中最小值时,
            // 它的值才一定是最优解。这就是一个简化版的dijkstra算法。
            if(d<dist[a][b])
            {
                dist[a][b]=d;
                
                //如果g中的值不等于理论值,则走到需要+1, 所以放在队尾
                if(g[ca][cb]!=cs[i]) q.push_back({a, b});
                //否则放在对头
                else q.push_front({a, b});
            }
        }
        
    }  
    
    return dist[n][m];//这里的n,m就是实打实的n,m
}

int main()
{
    int T;
    cin>>T;
    
    while(T--)
    {
        scanf("%d%d", &n, &m);
        // 从0行0列开始输入\ 和 /
        for(int i=0; i<n; i++) scanf("%s", g[i]);
        
        int t=bfs();
        
        //优化,和是奇数,直接无解
        if((n+m)&1) puts("NO SOLUTION");
        else printf("%d\n", t);
    }
    
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值