【888题竞赛篇】第四题,2023ICPC合肥-送外卖(Takeout Delivering)

这里写自定义目录标题

  • 更多精彩内容
    • 256题算法特训课,帮你斩获大厂60W年薪offer
  • 原题
    • 2023ICPC合肥-送外卖
    • B站动画详解
  • 问题分析
  • 思路分析
  • 算法实现
  • 代码详解
    • 标准代码程序
      • C++代码
      • Java代码
      • Python代码
      • Javascript代码
  • 复杂度分析
    • 时间复杂度
    • 空间复杂度
  • 总结

更多精彩内容

这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!

256题算法特训课,帮你斩获大厂60W年薪offer

原题

2023ICPC合肥-送外卖

B站动画详解

问题分析

题目要求在无向图中找到一条从节点 1 1 1 到节点 n n n 的路径,使得这条路径上的最大边权与次大边权之和最小。对于这个问题,显然不能简单地只看最短路径的计算,而是需要考虑路径中最大的边和次大边的组合情况。我们可以通过枚举每一条边作为路径上的最大边,并且预处理从起点(节点 1 1 1)和终点(节点 n n n)到每个节点的路径信息,进而得到最优解。这样可以将问题转化为“在固定最大边的前提下,寻找最优次大边”的问题。

思路分析

解决该问题可以采用基于 Dijkstra 算法的策略。具体步骤如下:

  1. 图的表示:将无向图表示为邻接表。每条边存储起点、终点和边权。

  2. 最优路径计算:为了预处理从起点和终点到图中各个节点的最大边权最小值,我们使用两次 Dijkstra 算法,分别从节点 1 1 1 和节点 n n n 出发,计算到所有节点的最大边权最小值。这一步将帮助我们在后续枚举边作为最大边时,快速获得与之对应的次大边。

  3. 枚举最大边:对每一条边,假设它是从节点 1 1 1 到节点 n n n 的路径上的最大边,那么此时的次大边应当是从起点到这条边的一个端点和从终点到这条边的另一个端点的最大边权的较小值。具体地,对于一条边 ( x , y , z ) (x, y, z) (x,y,z),设它是路径中的最大边,则次大边可以是从节点 1 1 1 x x x y y y 的最大边权,以及从节点 n n n x x x y y y 的最大边权的最小值。

  4. 更新最优解:对于每一条边,计算其最大边与次大边的和,并更新当前最优解。

  5. 输出结果:输出最优解,即路径中最大边与次大边的最小和。

通过这种方法,我们能够在较高效率下解决该问题。

算法实现

代码首先定义了图的结构,并通过邻接表存储每个节点及其相邻节点和边权。dij 函数用于执行 Dijkstra 算法,在从起点或终点出发时,记录到各个节点的最大边权。主函数部分首先进行数据输入,并构建图。接着通过两次 Dijkstra 算法计算节点 1 1 1 和节点 n n n 到其他节点的最大边权。然后遍历每条边,假设其为路径上的最大边,计算对应的次大边,并更新最优解。

代码详解

标准代码程序

C++代码

#include 
using namespace std;
const int N = 1e6 + 10;
struct node
{
	int x,y,z;
}e[N];
vector<pair<int,int> > G[N];
int d1[N],dn[N],n,m,vis[N],ans=INT_MAX;
void dij(int st,int *dis)
{
	for(int i=1;i<=n;i++) vis[i]=0,dis[i]=INT_MAX;
	priority_queue<pair<int,int>>q;
	dis[st]=0;
	q.push({0,st});
	while(q.size())
	{
		int w=-q.top().first;
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(auto v:G[u])
		{
			int to=v.first;
			int s=v.second;
			if(dis[to]>max(w,s))
			{
				dis[to]=max(w,s);
				q.push({-dis[to],to});
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) 
	{
		cin>>e[i].x>>e[i].y>>e[i].z;
		G[e[i].x].push_back({e[i].y,e[i].z});
		G[e[i].y].push_back({e[i].x,e[i].z});
		if((e[i].x==1&&e[i].y==n)||(e[i].x==n&&e[i].y==1)) ans=min(ans,e[i].z);
	}
	dij(1,d1);
	dij(n,dn);
	
	for(int i=1;i<=m;i++)
	{
		int x=e[i].x;
		int y=e[i].y;
		int Max=e[i].z;//假设当前这个是最大边,后面可以通过判断得知
		int subMax=min(max(d1[x],dn[y]),max(d1[y],dn[x]));//找次大边
		if (subMax<=Max) 
		{
			ans=min(ans,Max+subMax);
		}
	}
	cout<<ans;
}

Java代码

import java.util.*;

public class Main {
    static class Node {
        int x, y, z;
        Node(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    static final int N = 1000010;
    static List<int[]>[] G = new ArrayList[N];
    static int[] d1 = new int[N];
    static int[] dn = new int[N];
    static boolean[] vis = new boolean[N];
    static Node[] e = new Node[N];
    static int n, m;
    static int ans = Integer.MAX_VALUE;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();

        for (int i = 1; i <= n; i++) G[i] = new ArrayList<>();

        for (int i = 1; i <= m; i++) {
            int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();
            e[i] = new Node(u, v, w);
            G[u].add(new int[]{v, w});
            G[v].add(new int[]{u, w});
            if ((u == 1 && v == n) || (u == n && v == 1)) ans = Math.min(ans, w);
        }

        dij(1, d1);
        dij(n, dn);

        for (int i = 1; i <= m; i++) {
            int x = e[i].x;
            int y = e[i].y;
            int maxEdge = e[i].z;
            int subMax = Math.min(Math.max(d1[x], dn[y]), Math.max(d1[y], dn[x]));
            if (subMax <= maxEdge) ans = Math.min(ans, maxEdge + subMax);
        }

        System.out.println(ans);
    }

    static void dij(int start, int[] dist) {
        Arrays.fill(vis, false);
        Arrays.fill(dist, Integer.MAX_VALUE);
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        dist[start] = 0;
        pq.offer(new int[]{0, start});

        while (!pq.isEmpty()) {
            int[] curr = pq.poll();
            int w = -curr[0], u = curr[1];
            if (vis[u]) continue;
            vis[u] = true;

            for (int[] v : G[u]) {
                int to = v[0], s = v[1];
                if (dist[to] > Math.max(w, s)) {
                    dist[to] = Math.max(w, s);
                    pq.offer(new int[]{-dist[to], to});
                }
            }
        }
    }
}

Python代码

import heapq

N = int(1e6) + 10
G = [[] for _ in range(N)]
d1 = [float('inf')] * N
dn = [float('inf')] * N
e = []
vis = [False] * N

def dijkstra(st, dist):
    pq = []
    dist[st] = 0
    heapq.heappush(pq, (0, st))
    
    while pq:
        w, u = heapq.heappop(pq)
        w = -w
        if vis[u]:
            continue
        vis[u] = True
        
        for to, s in G[u]:
            if dist[to] > max(w, s):
                dist[to] = max(w, s)
                heapq.heappush(pq, (-dist[to], to))

def main():
    n, m = map(int, input().split())
    ans = float('inf')
    
    for _ in range(m):
        u, v, w = map(int, input().split())
        e.append((u, v, w))
        G[u].append((v, w))
        G[v].append((u, w))
        if (u == 1 and v == n) or (u == n and v == 1):
            ans = min(ans, w)
    
    dijkstra(1, d1)
    dijkstra(n, dn)
    
    for x, y, maxEdge in e:
        subMax = min(max(d1[x], dn[y]), max(d1[y], dn[x]))
        if subMax <= maxEdge:
            ans = min(ans, maxEdge + subMax)
    
    print(ans)

if __name__ == "__main__":
    main()

Javascript代码

const N = 1e6 + 10;
const G = Array.from({ length: N }, () => []);
const d1 = Array(N).fill(Infinity);
const dn = Array(N).fill(Infinity);
const vis = Array(N).fill(false);
const e = [];

function dijkstra(st, dist) {
    const pq = [];
    dist[st] = 0;
    pq.push([0, st]);

    while (pq.length > 0) {
        const [w, u] = pq.shift();
        const weight = -w;
        if (vis[u]) continue;
        vis[u] = true;

        for (const [to, s] of G[u]) {
            if (dist[to] > Math.max(weight, s)) {
                dist[to] = Math.max(weight, s);
                pq.push([-dist[to], to]);
            }
        }

        pq.sort((a, b) => a[0] - b[0]);
    }
}

function main() {
    const input = require("fs").readFileSync(0, "utf-8").split("\n");
    let [n, m] = input[0].split(" ").map(Number);
    let ans = Infinity;

    for (let i = 1; i <= m; i++) {
        const [u, v, w] = input[i].split(" ").map(Number);
        e.push([u, v, w]);
        G[u].push([v, w]);
        G[v].push([u, w]);
        if ((u === 1 && v === n) || (u === n && v === 1)) ans = Math.min(ans, w);
    }

    dijkstra(1, d1);
    dijkstra(n, dn);

    for (const [x, y, maxEdge] of e) {
        const subMax = Math.min(Math.max(d1[x], dn[y]), Math.max(d1[y], dn[x]));
        if (subMax <= maxEdge) ans = Math.min(ans, maxEdge + subMax);
    }

    console.log(ans);
}

main();

复杂度分析

时间复杂度

  1. Dijkstra 算法部分
    由于需要分别从节点 1 1 1 和节点 n n n 运行两次 Dijkstra 算法,时间复杂度为 O ( ( n + m ) log ⁡ n ) O((n + m) \log n) O((n+m)logn)。其中, n n n 是节点数, m m m 是边数。Dijkstra 算法使用优先队列,每次操作的复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

  2. 枚举边部分
    枚举每条边的复杂度为 O ( m ) O(m) O(m),因为需要遍历所有边来进行最大边和次大边的计算。

  3. 总体时间复杂度
    综合考虑,算法的总时间复杂度为 O ( ( n + m ) log ⁡ n + m ) O((n + m) \log n + m) O((n+m)logn+m)。对于这道题目,由于 m ≥ n m \geq n mn,因此最终时间复杂度为 O ( m log ⁡ n ) O(m \log n) O(mlogn)

空间复杂度

  1. 图的存储
    使用邻接表存储图,空间复杂度为 O ( n + m ) O(n + m) O(n+m)

  2. 距离数组和访问标记数组
    存储从节点 1 1 1 和节点 n n n 出发到各节点的距离,空间复杂度为 O ( n ) O(n) O(n)

  3. 其他辅助空间
    包括边的信息、优先队列等,空间复杂度也为 O ( m ) O(m) O(m)

  4. 总体空间复杂度
    总的空间复杂度为 O ( n + m ) O(n + m) O(n+m)

总结

这道题目考察了在无向图中寻找路径时,如何在兼顾多条边的权重组合的情况下找到最优解。我们通过 Dijkstra 算法计算出从起点和终点到其他节点的最大边权,然后结合枚举的方式找到最优路径。这种思路避免了暴力求解所有路径的高时间复杂度,并通过有效的预处理和枚举策略,将问题转化为针对特定边权的二次组合问题。算法的关键在于利用 Dijkstra 的最短路径特性,在确保计算效率的前提下,准确找到最大边和次大边的组合。该方法在处理大规模图的情况下依然能够有效运行,适用于边数较多的场景。最终的复杂度分析表明,这种方法可以在 O ( m log ⁡ n ) O(m \log n) O(mlogn) 的时间内完成计算,能够应对题目中给定的约束条件。

你可能感兴趣的:(java,c++,算法,数据结构,图论,icpc,算法竞赛)