六月集训(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); 
    }
};

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]);
    }
};

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值