单源最短路的扩展应用

文章介绍了两种方法处理最短路径问题,包括使用虚拟远点优化SPFA算法以及反向建图,展示了在AcWing题库中如何运用这些技术解决类似问题,如1137、173和383题目的解法
摘要由CSDN通过智能技术生成

1137. 选择最佳线路 - AcWing题库

原问题:从每个起点出发,到达终点的所有路线长度的最小值

加了虚拟远点之后:从虚拟远点出发,到达终点的所有路线的最小值      173. 矩阵距离 - AcWing题库(虚拟远点,思路一样)

法1:虚拟远点 (这里的idx一定要重置!!)

//虚拟远点
import java.util.*;
import java.io.*;

public class Main{
    static int N = 1010, M = 21010, INF = 0x3f3f3f3f;//这里M开多1000是因为可能建立了1000条虚拟远点边
    static int[] dist = new int[N];
    static int[] h = new int[N], e = new int[M], ne = new int[M], w = new int[M];
    static int n, m, s, idx;
    static boolean[] st = new boolean[N];//是否在循环队列中
    
    //邻接表存储
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static int spfa(){
        Arrays.fill(dist, INF);
        dist[0] = 0;
        Queue<Integer> q = new LinkedList<>();
        q.offer(0);
        
        while(!q.isEmpty()){
            int t = q.poll();
            
            st[t] = false;
            for(int i = h[t]; i != -1; i = ne[i]){
                int j = e[i];
                if(dist[j] > dist[t] + w[i]){
                    dist[j] = dist[t] + w[i];
                    if(!st[j]){
                        q.offer(j);
                        st[j] = true;
                    }
                }
            }
        }
        
        if(dist[s] == INF) return -1;
        else return dist[s];
    }
    
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            String temp = br.readLine();
            if(temp == null) break;//测试数据结束
            String[] arr = temp.split(" ");
            n = Integer.parseInt(arr[0]);
            m = Integer.parseInt(arr[1]);
            s = Integer.parseInt(arr[2]);
            
            Arrays.fill(h, -1);//初始化队头
            idx = 0;//这里的idx一定要记得重置
            
            //建图
            for(int i = 1; i <= m; i ++){
                String[] str = br.readLine().split(" ");
                int p = Integer.parseInt(str[0]);
                int q = Integer.parseInt(str[1]);
                int t = Integer.parseInt(str[2]);
                add(p, q, t);
            }
            
            int w = Integer.parseInt(br.readLine());//起点个数
            String[] s1 = br.readLine().split(" ");
            for(int i = 0; i < w; i ++){
                int x = Integer.parseInt(s1[i]);
                add(0, x, 0);//建立虚拟远点和起点的联系
            }
            System.out.println(spfa());
        }
    }
}

 法2:反向建图

import java.util.*;
import java.io.*;

public class Main{
    static int N = 1010, M = 20010, INF = 0x3f3f3f3f;
    static int n, m, S, idx, W;
    static int[] h = new int[N], ne = new int[M], e = new int[M], w = new int[M];
    static int[] dist = new int[N];
    static boolean[] st = new boolean[N];
    static int[] start = new int[N];//起点
    
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static void spfa(){
        Arrays.fill(dist, INF);
        dist[S] = 0;
        Queue<Integer> q = new LinkedList<>();
        q.offer(S);
        
        while(!q.isEmpty()){
            int t = q.poll();
            st[t] = false;
            
            for(int i = h[t]; i != -1; i = ne[i]){
                int j = e[i];
                if(dist[j] > dist[t] + w[i]){
                    dist[j] = dist[t] + w[i];
                    if(!st[j]){
                        q.offer(j);
                        st[j] = true;
                    }
                }
            }
        }
    }
    
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            String temp = br.readLine();
            if(temp == null) break;
            String[] s1 = temp.split(" ");
            n = Integer.parseInt(s1[0]);
            m = Integer.parseInt(s1[1]);
            S = Integer.parseInt(s1[2]);
            
            Arrays.fill(h, -1);//每次都要重置
            idx = 0;
            
            for(int i = 1; i <= m; i ++){
                String[] s2 = br.readLine().split(" ");
                int p = Integer.parseInt(s2[0]);
                int q = Integer.parseInt(s2[1]);
                int t = Integer.parseInt(s2[2]);
                add(q, p, t);
            }
            
            W = Integer.parseInt(br.readLine());
            String[] s3 = br.readLine().split(" ");
            for(int i = 0; i < W; i ++){
                start[i] = Integer.parseInt(s3[i]);
            }
            
            spfa();
            
            int res = INF;
            for(int i = 0; i < W; i ++){
                res = Math.min(res, dist[start[i]]);
            }
            
            if(res == INF) System.out.println("-1");
            else System.out.println(res);
        }
    }
}

1131. 拯救大兵瑞恩 - AcWing题库

 

import java.util.*;

class PII{
    int x, y;
    public PII(int x, int y){
        this.x = x;//这个格子的编号
        this.y = y;//所持有钥匙的状态
    }
}

public class Main{
    //M为4*n*(n-1)
    static int N = 11, M = 360, P = 1 << 10, INF = 0x3f3f3f3f;
    static int n, m, p, k, s, idx;
    static int[] h = new int[N * N], ne = new int[M], e = new int[M], w = new int[M];
    static int[][] g = new int[N * N][N * N];//g[x][y]表示x行y列的格子的编号
    static int[] key = new int[P];//格子的初始钥匙状态
    static int[][] dist = new int[N * N][P];//每个格子对应的编号所持有钥匙的状态
    static boolean[][] st = new boolean[N * N][P];//判断是否用过
    static boolean[][] line = new boolean[N * N][N * N];//用于建图,格子与格子之间连接的状态
    
    //邻接表存储
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    //剩余所有边连接
    public static void build(){
        int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
        
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                for(int u = 0; u < 4; u ++){
                    int x = i + dx[u];
                    int y = j + dy[u];
                    if(x < 1 || x > n || y < 1 || y > m) continue;
                    int a = g[i][j], b = g[x][y];
                    if(!line[a][b]) add(a, b, 0);
                }
            }
        }
    }
    
    public static int bfs(){
        for(int i = 0; i < N * N; i ++){
            Arrays.fill(dist[i], INF);
        }
        
        dist[1][0] = 0;//起点的距离是0,坐标(1,1)的编号是1,什么钥匙都没有,状态为0
        
        //双端队列
        Deque<PII> q = new LinkedList<>();
        q.offer(new PII(1, 0));
        
        while(!q.isEmpty()){
            PII t = q.poll();
            
            if(st[t.x][t.y]) continue;
            st[t.x][t.y] = true;
            
            if(t.x == n * m) return dist[t.x][t.y];//走到了终点
            
            //1.如果这个位置有钥匙,就要捡起来
            if(key[t.x] != 0){
                int state = t.y | key[t.x];
                if(dist[t.x][state] > dist[t.x][t.y]){
                    dist[t.x][state] = dist[t.x][t.y];
                    q.offerFirst(new PII(t.x, state));//0加入队头
                }
            }
            
            //2.更新这个点的所有出边
            for(int i = h[t.x]; i != -1; i = ne[i]){
                int j = e[i];
                //有门,没有钥匙
                if(w[i] != 0 && ((t.y >> w[i] - 1 & 1) == 0)) continue;
                
                if(dist[j][t.y] > dist[t.x][t.y] + 1){
                    dist[j][t.y] = dist[t.x][t.y] + 1;
                    q.offerLast(new PII(j, t.y));//1加入队尾
                }
            }
        }
        return -1;
    }
    
    //开始main函数
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        p = sc.nextInt();
        k = sc.nextInt();
        
        Arrays.fill(h, -1);
        
        for(int i = 1, t = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                g[i][j] = t ++;//给格子编号
            }
        }
        
        while(k -- > 0){
            int x1 = sc.nextInt();
            int y1 = sc.nextInt();
            int x2 = sc.nextInt();
            int y2 = sc.nextInt();
            int c = sc.nextInt();
            
            int a = g[x1][y1];
            int b = g[x2][y2];
            line[a][b] = true;
            line[b][a] = true;//标记为已经连接过边了,无论是墙还是门
            
            if(c != 0){
                add(a, b, c);//连接双向边,边权是门的型号
                add(b, a, c);
            }
        }
        
        build();//建立其他通路,不是门也不是墙
        
        s = sc.nextInt();//钥匙的数量
        while(s -- > 0){
            int x = sc.nextInt();
            int y = sc.nextInt();
            int c = sc.nextInt();
            //由于格子的编号是从1开始的
            key[g[x][y]] |= 1 << c - 1;
        }
        
        //最后输出bfs
        System.out.print(bfs());
    }
}

 

1134. 最短路计数 - AcWing题库

import java.util.*;
import java.io.*;

public class Main{
    static int N = 100010, M = 4 * N, INF = 0x3f3f3f3f;//M因为无向边,有可能有重边和自环
    static int n, m, idx;
    static int[] h = new int[N], e = new int[M], ne = new int[M];
    static int[] dist = new int[N];
    static int[] cnt = new int[N];
    static int[] q = new int[N];//bfs队列
    static boolean[] st = new boolean[N];
    
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static void bfs(){
        Arrays.fill(dist, INF);
        int hh = 0, tt = -1;
        q[++ tt] = 1;
        dist[1] = 0;
        cnt[1] = 1;
        
        while(hh <= tt){
            int t = q[hh ++];
            
            
            for(int i = h[t]; i != -1; i = ne[i]){
                int j = e[i];
                if(dist[j] > dist[t] + 1){
                    dist[j] = dist[t] + 1;
                    cnt[j] = cnt[t];
                    
                    if(!st[j]){
                        q[++ tt] = j;
                        st[j] = true;
                    }
                }else if(dist[j] == dist[t] + 1){
                    cnt[j] = (cnt[j] + cnt[t]) % 100003;
                }
            }
        }
    }
    
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] arr = br.readLine().split(" ");
        n = Integer.parseInt(arr[0]);
        m = Integer.parseInt(arr[1]);
        
        Arrays.fill(h, -1);
        
        while(m -- > 0){
            String[] str = br.readLine().split(" ");
            int a = Integer.parseInt(str[0]);
            int b = Integer.parseInt(str[1]);
            add(a, b);
            add(b, a);//无向图
        }
        
        bfs();
        
        for(int i = 1; i <= n; i ++){
            System.out.println(cnt[i]);
        }
    }
}

 

383. 观光 - AcWing题库

        先算出最短路径和最短路径的条数,次短路径和次短路径的条数,再判断最短路径是不是比次短路径多1,如果是的话,就把次短路径的条数加上,否则就不加。 

import java.util.*;
import java.io.*;

class PII implements Comparable<PII>{
    int ver, type, dist;
    public PII(int ver, int type, int dist){
        this.ver = ver;
        this.type = type;
        this.dist = dist;
    }
    public int compareTo(PII o){
        return dist - o.dist;
    }
}

public class Main{
    static int N = 1010, M = 10010, INF = 0x3f3f3f3f;
    static int n, m, S, F, idx;
    static int[] h = new int[N], e = new int[M], ne = new int[M], w = new int[M];
    static int[][] dist = new int[N][2];//dist[i][0]表示最短 dist[i][1]表示次短
    static int[][] cnt = new int[N][2];
    static boolean[][] st = new boolean[N][2];
    
    public static void add(int a, int b, int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    
    public static int dijkstra(){
        for(int i = 0; i < N; i ++){
            Arrays.fill(dist[i], INF);
            Arrays.fill(st[i], false);
            Arrays.fill(cnt[i], 0);
        }
        
        PriorityQueue<PII> q = new PriorityQueue<>();//优先队列
        q.offer(new PII(S, 0, 0));//起点,类型为最短,还没有距离
        
        dist[S][0] = 0;//起点只有最短距离,没有次短距离
        cnt[S][0] = 1;//1条最短距离
        
        while(!q.isEmpty()){
            PII t = q.poll();
            
            int ver = t.ver;
            int type = t.type;
            int distance = t.dist;
            int count = cnt[ver][type];
            
            if(st[ver][type]) continue;
            st[ver][type] = true;
            
            for(int i = h[ver]; i != -1; i = ne[i]){
                int j = e[i];
                if(dist[j][0] > distance + w[i]){
                    dist[j][1] = dist[j][0];
                    dist[j][0] = distance + w[i];
                    cnt[j][1] = cnt[j][0];
                    cnt[j][0] = count;
                    q.offer(new PII(j, 1, dist[j][1]));
                    q.offer(new PII(j, 0, dist[j][0]));
                }else if(dist[j][0] == distance + w[i]){
                    cnt[j][0] += count;
                }else if(dist[j][1] > distance + w[i]){
                    dist[j][1] = distance + w[i];
                    cnt[j][1] = count;
                    q.offer(new PII(j, 1, dist[j][1]));
                }else if(dist[j][1] == distance + w[i]){
                    cnt[j][1] += count;
                }
            }
        }
        int res = cnt[F][0];
        if(dist[F][0] + 1 == dist[F][1]) res += cnt[F][1];
        
        return res;
    }
    
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        while(T -- > 0){
            String[] s1 = br.readLine().split(" ");
            n = Integer.parseInt(s1[0]);
            m = Integer.parseInt(s1[1]);
            
            Arrays.fill(h, -1);
            idx = 0;
            
            while(m -- > 0){
                String[] s2 = br.readLine().split(" ");
                int a = Integer.parseInt(s2[0]);
                int b = Integer.parseInt(s2[1]);
                int c = Integer.parseInt(s2[2]);
                add(a, b, c);//有向边
            }
            String[] s3 = br.readLine().split(" ");
            S = Integer.parseInt(s3[0]);
            F = Integer.parseInt(s3[1]);
            
            System.out.println(dijkstra());
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值