• 六月集训(27) 图


    1.LeetCode:1514. 概率最大的路径

    原题链接


            给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i] 。

            指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。

            如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。

            示例 1:

            输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2

            输出:0.25000

            示例 2:

            输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2

            输出:0.30000

            示例 3:

            输入:n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2

            输出:0.00000

            提示:

            2 <= n <= 10^4

            0 <= start, end < n

            start != end

            0 <= a, b < n

            a != b

            0 <= succProb.length == edges.length <= 2*10^4

            0 <= succProb[i] <= 1

            每两个节点之间最多有一条边


            这道题就是寻找一条从start到end的最长路,那么寻找最长路的方法有很多,这里介绍一下spfa的做法。

            首先事根据edges建立双向边,然后从strat到end进行spfa。spfa的一般过程就是遍历图的所有点,并在过程中维护从start到该点的最短或者最大距离。那么这样就需要一个dist数组用来记录距离,由于这里求的是最大者,所以所有dist初始化为0。

            接着把start加入队列,dist[start]=0。搜索的过程就很简单了,当队列不为空时,取出对头作为当前的起点,遍历他的所有边计算并维护最大值,如果到目标点的距离比之前大就更新目标点距离并让他入队。

            由于这里会多次到达end点,所以我们每次到达end的时候就维护一个ans,每次选择ans和dist[end]的最大值即可。

            当搜索结束的时候返回ans。

    class Solution {
        #define maxn 10005
        struct Edge{
            int v;
            double w;
            Edge(){}
            Edge(int _v,double _w): v(_v),w(_w){}
        };
        vector<Edge> e[maxn];
        
        double spfa(int start,int end){
            double dist[maxn];
            memset(dist,0,sizeof(dist));
            queue<int> q;
            q.push(start);
            dist[start]=1;
            double ans=0;
            while(!q.empty())
            {
                int u=q.front();
                q.pop();
                if(u==end){
                    ans=max(ans,dist[end]);
                }
                for(int i=0;i<e[u].size();++i){
                    int v=e[u][i].v;
                    double tmp=dist[u]*e[u][i].w;
                    if(tmp>dist[v]){
                        dist[v]=tmp;
                        q.push(v);
                    }
                }
            }
            return ans;
        }
    
    public:
        double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start, int end) {
            for(int i=0;i<edges.size();++i){
               int u=edges[i][0];
               int v=edges[i][1];
               double w=succProb[i];
               e[u].push_back(Edge(v,w));
               e[v].push_back(Edge(u,w));
            }
            return spfa(start,end); 
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    2.LeetCode:LCP 56. 信物传送

    原题链接


            欢迎各位勇者来到力扣城,本次试炼主题为「信物传送」。

            本次试炼场地设有若干传送带,matrix[i][j] 表示第 i 行 j 列的传送带运作方向,“^”,“v”,“<”,“>” 这四种符号分别表示 上、下、左、右 四个方向。信物会随传送带的方向移动。勇者每一次施法操作,可临时变更一处传送带的方向,在物品经过后传送带恢复原方向

            通关信物初始位于坐标 start处,勇者需要将其移动到坐标 end 处,请返回勇者施法操作的最少次数。

            注意:

            start 和 end 的格式均为 [i,j]

            示例 1:

            输入:matrix = [“>>v”,“v^<”,“<><”], start = [0,1], end = [2,0]

            输出:1

            示例 2:

            输入:matrix = [“>>v”,“>>v”,“^<<”], start = [0,0], end = [1,1]

            输出:0

            示例 3:

            输入:matrix = [“>^>“,”<v>”,“v<”], start = [0,0], end = [1,3]

            输出:3

            提示:

            matrix 中仅包含 ‘^’、‘v’、‘<’、‘>’

            0 < matrix.length <= 100

            0 < matrix[i].length <= 100

            0 <= start[0],end[0] < matrix.length

            0 <= start[1],end[1] < matrix[i].length


            这道题是求最短路,本题的关键在于修改方向该怎么处理,也就是如何维护dist数组。

            由于是矩阵,那么自然的可以向上下左右四个方向搜索,既然这样我们就把矩阵的方向映射成数字,来与我们的方向数组的方向对应上,(比如方向数组的0,1,2,3,分别代表着上下左右,那么我们就把矩阵对应的方向映射成他们在方向数组的下标)这样做是为了之后维护dist数组提供方便。同时由于这里是矩阵的spfa,dist数组既可以建立成一维,也可以建立成二维,这里我选择将二维映射成一维,方法也多次提到了行坐标 * 列数 + 列坐标。

            当做好准备工作之后,依然是spfa。具体过程为,由于这里求得是最短路,所以dist数组初始化均为0x3f3f3f(多次提到该数字,既便于初始化又足够得大并且支持*2操作)。

            具体搜索过程为首先dist[start]=0,start入队,在搜索的过程中我们得到队首 u 的坐标后开始向四个方向搜索,如果越界直接返回,没有越界就选择更新到下一点 v 的距离,这里方向数组下标 i就是我们要前往的方向,如果原状态 u 的方向恰好与 i 相同,那么显然是不需要花费距离的,则有dist[v]=dist[u],而如果方向不同,就需要改变方向到达,这时就有dist[v]=dist[u]+1。这里我们就发现了,到达下一点距离取决于我们搜索的方向和 u 这一点的方向是否相同,相同可以直接到达,否则就改变方向,距离+1。

            当距离更新完毕之后,如果小于下一点之前的距离更新并且入队。搜索完毕我们返回到达end的dist即可。

    class Solution {
        #define maxn 10005
        int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
    
        int spfa(int grid[110][110],int m,int n,int start,int end){
            int dist[maxn];
            memset(dist,0x3f,sizeof(dist));
            queue<int> q;
            q.push(start);
            dist[start]=0;
            while(!q.empty()){
                int u=q.front();
                q.pop();
                int x=u/n;
                int y=u%n;
                for(int i=0;i<4;++i){
                    int tx=x+dir[i][0];
                    int ty=y+dir[i][1];
                    if(tx==-1||ty==-1||tx==m||ty==n){
                        continue;
                    }
                    int mask=tx*n+ty;
                    int tmp=dist[u]+(grid[x][y]==i?0:1);
                    if(tmp<dist[mask]){
                        dist[mask]=tmp;
                        q.push(mask);
                    }
                }
            }
            return dist[end];
        }
    
    public:
        int conveyorBelt(vector<string>& matrix, vector<int>& start, vector<int>& end) {
            int i,j;
            int m=matrix.size();
            int n=matrix[0].size();
            int grid[110][110];
            for(i=0;i<m;++i){
                for(j=0;j<n;++j){
                    if(matrix[i][j]=='^'){
                        grid[i][j]=0;
                    }else if(matrix[i][j]=='>'){
                        grid[i][j]=1;
                    }else if(matrix[i][j]=='v'){
                        grid[i][j]=2;
                    }else if(matrix[i][j]=='<'){
                        grid[i][j]=3;
                    }
                }   
            }
            return spfa(grid,m,n,start[0]*n+start[1],end[0]*n+end[1]);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    3.LeetCode:1377. T 秒后青蛙的位置

    原题链接


            给你一棵由 n 个顶点组成的无向树,顶点编号从 1 到 n。青蛙从 顶点 1 开始起跳。规则如下:

            在一秒内,青蛙从它所在的当前顶点跳到另一个 未访问 过的顶点(如果它们直接相连)。

            青蛙无法跳回已经访问过的顶点。

            如果青蛙可以跳到多个不同顶点,那么它跳到其中任意一个顶点上的机率都相同。

            如果青蛙不能跳到任何未访问过的顶点上,那么它每次跳跃都会停留在原地。

            无向树的边用数组 edges 描述,其中 edges[i] = [fromi, toi] 意味着存在一条直接连通 fromi 和 toi 两个顶点的边。

            返回青蛙在 t 秒后位于目标顶点 target 上的概率。

            示例 1:

            输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 2, target = 4

            输出:0.16666666666666666 。

            示例 2:

            输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 1, target = 7

            输出:0.3333333333333333

            提示:

            1 <= n <= 100

            edges.length == n - 1

            edges[i].length == 2

            1 <= ai, bi <= n

            1 <= t <= 50

            1 <= target <= n


            这一题首先要注意一点,我们搜索的是一个树,再加上题目要求不能返回,所以到达目标点的路径有且只有一个(树无环),那么本题计算所有概率并累加的方法就显然是错的离谱的方法了。

            由于树也是图,所以这里我们先建立双向边,然后开始从1到target开始dfs。首先我们要得到该点能够到达的点的数量,由于题目要求不能返回所以要除去父那一条边(这里dfs的参数中要记录每个搜索点的父,既方便了求数量也方便了搜索)。递归终止的条件为step==maxstep( t )。更新答案的条件是当当前搜索的点u == target的时候我们去判断更新答案的两个条件:

            (1):该点就是目标点,没有能够再移动的边了并且移动的步数step<=maxstep,这对应着最后一个规则。当step<maxstep的时候我们可以一直在原题跳到最大能到移动的步数,也就意味着不用再往后搜索了,这里直接让step=maxstep,如果恰好等于了maxstep当然更好,我们更新ans。

            (2):如果该点是目标点,还能够继续搜索但是步数已经到达了最大步数直接更新ans。(中间搜索到的点不能原地停留)

            搜索的过程就是让当前点的每个边走一遍dfs(该点不为父)。那么这样我们的dfs应该拥有的参数为当前点 u ,u的父结点 fa, 到达当前点的距离p (p的更新为 p*1.0/cnt ),当前已经移动的步数step,最大步数maxstep,目标点target。

    class Solution {
        #define maxn 110
        vector<int> e[maxn];
        double ans=0;
        void dfs(int u,int fa,double p,int step,int maxstep,int target){
           int cnt=fa?e[u].size()-1:e[u].size();
           if(u==target){
               if(!cnt&&step<=maxstep){
                   ans=p;
                   step=maxstep;
               }else if(cnt&&step==maxstep){
                   ans=p;
               }
           }
           if(step==maxstep){
               return;
           }
           for(int i=0;i<e[u].size();++i){
               if(e[u][i]!=fa){
                   dfs(e[u][i],u,p*1.0/cnt,step+1,maxstep,target);
               }
           }
        }
    public:
        double frogPosition(int n, vector<vector<int>>& edges, int t, int target) {
            ans=0;
            for(int i=0;i<edges.size();++i){
                int u=edges[i][0];
                int v=edges[i][1];
                e[u].push_back(v);
                e[v].push_back(u);
            }
            dfs(1,0,1,0,t,target);
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 相关阅读:
    第五章:最新版零基础学习 PYTHON 教程—Python 字符串操作指南(第四节 - Python 中的字符串反转6种不同的方式方法)
    java一体化智慧工地信息管理平台源码 智慧工地APP源码
    Linux查看文件内容的几种方法
    分布式任务系统xxl-job
    Android红外功能模拟触摸鼠标事件唤醒屏幕
    SQL注入详解
    自动化测试框架有哪几种?搭建的思路是什么?完整指南奉上!
    sqlmap直接嗦 dnslog注入 sqllibs第8关
    CNN经典网络模型详解LeNet(LeNet-1, LeNet-4, LeNet-5最详细, Boosted LeNet-4)发展和过程
    springboot基础(51):整合kafka
  • 原文地址:https://blog.csdn.net/m0_67739923/article/details/125477128