多多送快递
拼多多技术岗 8月2号笔试 第三题
题目内容
多多在一家电商平台做物流调度。平台在 NNN 个城市之间建立了 MMM 条有向运输线路,每条线路从城市 uuu 到城市 vvv,需要支付邮费 www 元。
一位顾客在城市 111 下单,商品需要从城市 111 运送到城市 NNN。多多手里恰好有一张免邮券,可以免除任意一条两个城市间有向运输线路的邮费(将该条线路的邮费变为 000)。
请帮助多多计算从城市 111 到城市 NNN 的最小总邮费。如果即使使用免邮券也无法到达城市 NNN,输出 −1-1−1。
输入描述
第一行两个整数 N,MN, MN,M,分别表示城市数量和运输线路数量。
其中 NNN 表示共有 NNN 个城市,MMM 表示共有 MMM 条有向运输线路。 (2≤N≤100000, 0≤M≤200000)(2 \le N \le 100000,\ 0 \le M \le 200000)(2≤N≤100000, 0≤M≤200000)
接下来 MMM 行,每行三个整数 u,v,wu, v, wu,v,w,表示一条从城市 uuu 到城市 vvv 的有向运输线路,其中 www 表示通过该线路需要支付的邮费。 (1≤u,v≤N, 1≤w≤10000)(1 \le u, v \le N,\ 1 \le w \le 10000)(1≤u,v≤N, 1≤w≤10000)
输出描述
输出一个整数,表示从城市 111 到城市 NNN 的最小总邮费。如果无法到达,输出 −1-1−1。
样例1
输入
4 4
1 2 2
1 3 5
2 4 3
3 4 1
输出
1
说明
- 不使用免邮券:最短路径 1→2→4=2+3=51 \to 2 \to 4 = 2+3=51→2→4=2+3=5,或 1→3→4=5+1=61 \to 3 \to 4 = 5+1=61→3→4=5+1=6,最小为 555
- 免邮 1→31 \to 31→3(邮费 5→05 \to 05→0):走 1→3→4=0+1=11 \to 3 \to 4 = 0+1=11→3→4=0+1=1
- 免邮 1→21 \to 21→2(邮费 2→02 \to 02→0):走 1→2→4=0+3=31 \to 2 \to 4 = 0+3=31→2→4=0+3=3
- 免邮 2→42 \to 42→4(邮费 3→03 \to 03→0):走 1→2→4=2+0=21 \to 2 \to 4 = 2+0=21→2→4=2+0=2
- 免邮 3→43 \to 43→4(邮费 1→01 \to 01→0):走 1→3→4=5+0=51 \to 3 \to 4 = 5+0=51→3→4=5+0=5
最优方案:免邮 1→31 \to 31→3,总邮费 111。
题解
思路
解题思路: 最短路算法
- 根据是否使用过免邮,拆分状态,创建虚拟节点
1 - N的节点,表示到达该城市,还没有使用免邮卷N + 1 - 2n虚拟节点,已经使用过免邮卷
- 按照状态拆分之后,遍历原边
u ->v w构造三种情况边- 到达u之前未使用免邮,到达v不使用免邮卷
u -> v w - 到达u之前未使用免邮,到达v使用免邮卷
u -> v+N 0 - 到达u之前使用过免邮,到达v使用免邮卷
u + N -> v+N 0
- 到达u之前未使用免邮,到达v不使用免邮卷
- 上述处理之后添加虚拟边 + 虚拟节点之后就是处理最短路
Dijkstra的模板题了。 - 最终答案为
dist[N]不使用免邮 和dist[2 * N]使用免邮的最小值。 - 代码总体时间复杂度为
O((N + M)logN)
C++
cpp
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct Node {
ll u; // T - X
ll v; // T + X
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
vector<Node> a(N);
// Ti - Tj > |Xi - Xj| => Ti - Xi > Tj - Xj and Ti + Xi > Tj + Xj
for (int i = 0; i < N; i++) {
ll T,X;
cin >> T >> X;
a[i].u = T - X;
a[i].v = T + X;
}
sort(a.begin(), a.end(), [](const Node& a, const Node& b) {
if (a.u != b.u) {
return a.u < b.u;
}
return a.v > b.v;
});
// 求v的严格递增子序列长度 lis[i]代表长度为i的可取最小值
vector<ll> lis;
for (auto &p : a) {
ll x = p.v;
auto it = lower_bound(lis.begin(), lis.end(), x);
if (it == lis.end()) {
lis.push_back(x);
} else {
*it = x;
}
}
cout << lis.size() << endl;
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
static class Edge {
int to;
int w;
Edge(int to, int w) {
this.to = to;
this.w = w;
}
}
static class Node implements Comparable<Node> {
int id;
long dist;
Node(int id, long dist) {
this.id = id;
this.dist = dist;
}
// 小根堆
@Override
public int compareTo(Node other) {
return Long.compare(this.dist, other.dist);
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] first = br.readLine().split(" ");
int N = Integer.parseInt(first[0]);
int M = Integer.parseInt(first[1]);
// 分层图
// 1 ~ N : 未使用免邮券
// N+1 ~ 2N : 已经使用免邮券
ArrayList<Edge>[] graph = new ArrayList[2 * N + 1];
for (int i = 1; i <= 2 * N; i++) {
graph[i] = new ArrayList<>();
}
for (int i = 0; i < M; i++) {
String[] line = br.readLine().split(" ");
int u = Integer.parseInt(line[0]);
int v = Integer.parseInt(line[1]);
int w = Integer.parseInt(line[2]);
// 不使用免邮
graph[u].add(new Edge(v, w));
// 使用免邮 跳到v的虚拟节点
graph[u].add(new Edge(v + N, 0));
// 使用免邮之后 只能跳到v的虚拟节点
graph[u + N].add(new Edge(v + N, w));
}
long INF = Long.MAX_VALUE / 4;
long[] dist = new long[2 * N + 1];
Arrays.fill(dist, INF);
PriorityQueue<Node> pq = new PriorityQueue<>();
dist[1] = 0;
pq.offer(new Node(1, 0));
while (!pq.isEmpty()) {
Node cur = pq.poll();
int u = cur.id;
// 过期
if (cur.dist != dist[u]) {
continue;
}
for (Edge e : graph[u]) {
int v = e.to;
long nd = cur.dist + e.w;
if (dist[v] > nd) {
dist[v] = nd;
pq.offer(new Node(v, nd));
}
}
}
// 取使用免邮不免邮的较小值
long ans = Math.min(dist[N], dist[2 * N]);
if (ans == INF) {
System.out.println(-1);
} else {
System.out.println(ans);
}
}
}
python
python
import sys
import heapq
class Edge:
def __init__(self, to, w):
self.to = to
self.w = w
input = sys.stdin.readline
N, M = map(int, input().split())
# 分层图
# 1 ~ N : 未使用免邮券
# N+1 ~ 2N : 已经使用免邮券
graph = [[] for _ in range(2 * N + 1)]
for _ in range(M):
u, v, w = map(int, input().split())
# 不使用免邮
graph[u].append(Edge(v, w))
# 使用免邮 跳到v的虚拟节点
graph[u].append(Edge(v + N, 0))
# 使用免邮之后 只能跳到v的虚拟节点
graph[u + N].append(Edge(v + N, w))
INF = 4 * 10 ** 18
dist = [INF] * (2 * N + 1)
# 小根堆
pq = []
dist[1] = 0
heapq.heappush(pq, (0, 1))
while pq:
curDist, u = heapq.heappop(pq)
# 过期
if curDist != dist[u]:
continue
for e in graph[u]:
v = e.to
nd = curDist + e.w
if dist[v] > nd:
dist[v] = nd
heapq.heappush(pq, (nd, v))
# 取使用免邮不免邮的较小值
ans = min(dist[N], dist[2 * N])
if ans == INF:
print(-1)
else:
print(ans)
javascript
js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on("line", line => {
input.push(line);
});
rl.on("close", () => {
let idx = 0;
let [N, M] = input[idx++].split(" ").map(Number);
// 分层图
// 1 ~ N : 未使用免邮券
// N+1 ~ 2N : 已经使用免邮券
let graph = Array.from(
{ length: 2 * N + 1 },
() => []
);
for (let i = 0; i < M; i++) {
let [u, v, w] = input[idx++].split(" ").map(Number);
// 不使用免邮
graph[u].push([v, w]);
// 使用免邮 跳到v的虚拟节点
graph[u].push([v + N, 0]);
// 使用免邮之后 只能跳到v的虚拟节点
graph[u + N].push([v + N, w]);
}
let INF = 4e18;
let dist = Array(2 * N + 1).fill(INF);
// 小根堆
// javascript 没有内置堆,手写一个
class MinHeap {
constructor() {
this.heap = [];
}
push(node) {
this.heap.push(node);
let i = this.heap.length - 1;
while (i > 0) {
let p = Math.floor((i - 1) / 2);
if (this.heap[p][0] <= this.heap[i][0])
break;
[this.heap[p], this.heap[i]] =
[this.heap[i], this.heap[p]];
i = p;
}
}
pop() {
if (this.heap.length === 1)
return this.heap.pop();
let res = this.heap[0];
this.heap[0] = this.heap.pop();
let i = 0;
while (true) {
let left = i * 2 + 1;
let right = i * 2 + 2;
let smallest = i;
if (
left < this.heap.length &&
this.heap[left][0] < this.heap[smallest][0]
) {
smallest = left;
}
if (
right < this.heap.length &&
this.heap[right][0] < this.heap[smallest][0]
) {
smallest = right;
}
if (smallest === i)
break;
[this.heap[i], this.heap[smallest]] =
[this.heap[smallest], this.heap[i]];
i = smallest;
}
return res;
}
isEmpty() {
return this.heap.length === 0;
}
}
let pq = new MinHeap();
dist[1] = 0;
pq.push([0, 1]);
while (!pq.isEmpty()) {
let [curDist, u] = pq.pop();
// 过期
if (curDist !== dist[u])
continue;
for (let e of graph[u]) {
let v = e[0];
let w = e[1];
let nd = curDist + w;
if (dist[v] > nd) {
dist[v] = nd;
pq.push([nd, v]);
}
}
}
// 取使用免邮不免邮的较小值
let ans = Math.min(dist[N], dist[2 * N]);
if (ans === INF) {
console.log(-1);
} else {
console.log(ans);
}
});
Go
go
package main
import (
"bufio"
"container/heap"
"fmt"
"os"
)
type Edge struct {
to int
w int
}
type Node struct {
id int
dist int64
}
// 小根堆
type PriorityQueue []Node
func (pq PriorityQueue) Len() int {
return len(pq)
}
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].dist < pq[j].dist
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}
func (pq *PriorityQueue) Push(x interface{}) {
*pq = append(*pq, x.(Node))
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
*pq = old[:n-1]
return item
}
func main() {
in := bufio.NewReader(os.Stdin)
var N, M int
fmt.Fscan(in, &N, &M)
// 分层图
// 1 ~ N : 未使用免邮券
// N+1 ~ 2N : 已经使用免邮券
graph := make([][]Edge, 2*N+1)
for i := 0; i < M; i++ {
var u, v, w int
fmt.Fscan(in, &u, &v, &w)
// 不使用免邮
graph[u] = append(graph[u], Edge{v, w})
// 使用免邮 跳到v的虚拟节点
graph[u] = append(graph[u], Edge{v + N, 0})
// 使用免邮之后 只能跳到v的虚拟节点
graph[u+N] = append(graph[u+N], Edge{v + N, w})
}
const INF int64 = 4e18
dist := make([]int64, 2*N+1)
for i := range dist {
dist[i] = INF
}
pq := &PriorityQueue{}
heap.Init(pq)
dist[1] = 0
heap.Push(pq, Node{1, 0})
for pq.Len() > 0 {
cur := heap.Pop(pq).(Node)
u := cur.id
// 过期
if cur.dist != dist[u] {
continue
}
for _, e := range graph[u] {
v := e.to
nd := cur.dist + int64(e.w)
if dist[v] > nd {
dist[v] = nd
heap.Push(pq, Node{v, nd})
}
}
}
// 取使用免邮不免邮的较小值
ans := dist[N]
if dist[2*N] < ans {
ans = dist[2*N]
}
if ans == INF {
fmt.Println(-1)
} else {
fmt.Println(ans)
}
}