题目链接
题目描述
给定一个带权无向连通图和 条从顶点 1 出发的计划修建的道路。问最多可以删除多少条计划道路,使得从顶点 1 到所有其他顶点的最短路径长度,与全部
条道路都修建时的情况相比,保持不变。
解题思路
这个问题的核心是找出哪些计划道路是“不可或缺”的,然后从总数中减去这些必要的道路。
我们可以将问题分解为两个主要步骤:
-
确定最终的最短路径:首先,我们需要知道在最理想的情况下(即所有
条计划道路都修建后),从顶点 1 到其他所有顶点的最短路径长度是多少。这是一个典型的单源最短路径问题,由于所有边权非负,我们可以使用 Dijkstra 算法来解决。我们在包含原始边和所有计划边的完整图上运行 Dijkstra,得到最终的最短距离数组
。
-
识别必要的计划道路:对于一条计划道路
,它什么时候是“必要的”?
- 首先,这条路必须本身是一条最短路,即它的权值
必须等于顶点 1 到
的最终最短距离
。如果
,说明存在其他更短的路径,这条计划道路就毫无用处,可以删除。
- 其次,即使
,这条路也未必是必要的。可能还存在另一条不依赖此路的、长度同样为
的路径。这种替代路径的最后一段必然是一条原始图中的边,例如
。这个条件可以用 Dijkstra 算法的松弛条件来验证:是否存在一个
在原始图中的邻居
,满足
。
- 首先,这条路必须本身是一条最短路,即它的权值
综合以上两点,一条连接到顶点 的计划道路是必要的,当且仅当:
- 所有连接到
的计划道路中,权值最小的那条等于
。
- 并且,对于所有在原始图中与
相连的邻居
,都不满足
。
换言之,如果到达顶点 的最短路径无法通过原始图中的任何一条边来“拼接”完成,那么它就必须直接依赖于一条从顶点 1 出发的计划道路。
算法流程
- 构建包含原始边和所有计划边的完整图。
- 在完整图上从顶点 1 运行 Dijkstra 算法,计算出
数组。
- 初始化必要道路计数
necessary_roads = 0
。 - 遍历每一个从 1 出发的计划道路
。
- 如果一条计划道路满足
,则将其视为一条“候选必要”道路。
- 对每一个有“候选必要”道路的顶点
进行检查: a. 判断是否存在一条原始边
满足
。 b. 如果不存在这样的原始边,说明要达到
的最短距离,必须依赖一条从 1 直连到
的计划道路。因此,我们将
necessary_roads
加一。注意,对于同一个顶点,即使有多条满足条件的计划道路,我们也只计数一次,因为只需保留其中一条即可。
- 最终答案为
。
代码
#include <iostream>
#include <vector>
#include <queue>
#include <map>
using namespace std;
using ll = long long;
using P = pair<ll, int>;
const ll INF = 1e18;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m, k;
cin >> n >> m >> k;
vector<vector<P>> base_adj(n + 1);
vector<vector<P>> full_adj(n + 1);
vector<P> planned_edges;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
base_adj[u].push_back({v, w});
base_adj[v].push_back({u, w});
full_adj[u].push_back({v, w});
full_adj[v].push_back({u, w});
}
for (int i = 0; i < k; ++i) {
int u, w;
cin >> u >> w;
planned_edges.push_back({u, w});
full_adj[1].push_back({u, w});
full_adj[u].push_back({1, w});
}
vector<ll> d_final(n + 1, INF);
priority_queue<P, vector<P>, greater<P>> pq;
d_final[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [dist, u] = pq.top();
pq.pop();
if (dist > d_final[u]) {
continue;
}
for (auto& edge : full_adj[u]) {
int v = edge.first;
int w = edge.second;
if (d_final[u] + w < d_final[v]) {
d_final[v] = d_final[u] + w;
pq.push({d_final[v], v});
}
}
}
int necessary_roads = 0;
map<int, bool> dest_needs_planned_road;
for (const auto& edge : planned_edges) {
int u = edge.first;
int w = edge.second;
if (d_final[u] == (ll)w) {
dest_needs_planned_road[u] = true;
}
}
for(auto const& [u, needs_check] : dest_needs_planned_road){
if(needs_check){
bool achievable_by_base = false;
for(const auto& base_edge : base_adj[u]){
int v = base_edge.first;
int w = base_edge.second;
if(d_final[v] + w == d_final[u]){
achievable_by_base = true;
break;
}
}
if(!achievable_by_base){
necessary_roads++;
}
}
}
cout << k - necessary_roads << endl;
return 0;
}
import java.util.*;
public class Main {
static class Edge {
int to;
int weight;
Edge(int to, int weight) {
this.to = to;
this.weight = weight;
}
}
static class State implements Comparable<State> {
long dist;
int u;
State(long dist, int u) {
this.dist = dist;
this.u = u;
}
@Override
public int compareTo(State other) {
return Long.compare(this.dist, other.dist);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int k = sc.nextInt();
List<List<Edge>> baseAdj = new ArrayList<>();
List<List<Edge>> fullAdj = new ArrayList<>();
for (int i = 0; i <= n; i++) {
baseAdj.add(new ArrayList<>());
fullAdj.add(new ArrayList<>());
}
List<Edge> plannedEdges = new ArrayList<>();
for (int i = 0; i < m; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int w = sc.nextInt();
baseAdj.get(u).add(new Edge(v, w));
baseAdj.get(v).add(new Edge(u, w));
fullAdj.get(u).add(new Edge(v, w));
fullAdj.get(v).add(new Edge(u, w));
}
for (int i = 0; i < k; i++) {
int u = sc.nextInt();
int w = sc.nextInt();
plannedEdges.add(new Edge(u, w));
fullAdj.get(1).add(new Edge(u, w));
fullAdj.get(u).add(new Edge(1, w));
}
long[] dFinal = new long[n + 1];
Arrays.fill(dFinal, Long.MAX_VALUE);
PriorityQueue<State> pq = new PriorityQueue<>();
dFinal[1] = 0;
pq.offer(new State(0, 1));
while (!pq.isEmpty()) {
State current = pq.poll();
long dist = current.dist;
int u = current.u;
if (dist > dFinal[u]) {
continue;
}
for (Edge edge : fullAdj.get(u)) {
int v = edge.to;
int w = edge.weight;
if (dFinal[u] + w < dFinal[v]) {
dFinal[v] = dFinal[u] + w;
pq.offer(new State(dFinal[v], v));
}
}
}
int necessaryRoads = 0;
Set<Integer> candidateDests = new HashSet<>();
for(Edge edge : plannedEdges){
if(dFinal[edge.to] == edge.weight){
candidateDests.add(edge.to);
}
}
for(int u : candidateDests){
boolean achievableByBase = false;
for(Edge baseEdge : baseAdj.get(u)){
int v = baseEdge.to;
int w = baseEdge.weight;
if(dFinal[v] != Long.MAX_VALUE && dFinal[v] + w == dFinal[u]){
achievableByBase = true;
break;
}
}
if(!achievableByBase){
necessaryRoads++;
}
}
System.out.println(k - necessaryRoads);
}
}
import sys
import heapq
def main():
input = sys.stdin.readline
n, m, k = map(int, input().split())
base_adj = [[] for _ in range(n + 1)]
full_adj = [[] for _ in range(n + 1)]
planned_edges = []
for _ in range(m):
u, v, w = map(int, input().split())
base_adj[u].append((v, w))
base_adj[v].append((u, w))
full_adj[u].append((v, w))
full_adj[v].append((u, w))
for _ in range(k):
u, w = map(int, input().split())
planned_edges.append((u, w))
full_adj[1].append((u, w))
full_adj[u].append((1, w))
d_final = [float('inf')] * (n + 1)
pq = [(0, 1)]
d_final[1] = 0
while pq:
dist, u = heapq.heappop(pq)
if dist > d_final[u]:
continue
for v, w in full_adj[u]:
if d_final[u] + w < d_final[v]:
d_final[v] = d_final[u] + w
heapq.heappush(pq, (d_final[v], v))
necessary_roads = 0
candidate_dests = set()
for u, w in planned_edges:
if d_final[u] == w:
candidate_dests.add(u)
for u in candidate_dests:
achievable_by_base = False
for v, w_base in base_adj[u]:
if d_final[v] != float('inf') and d_final[v] + w_base == d_final[u]:
achievable_by_base = True
break
if not achievable_by_base:
necessary_roads += 1
print(k - necessary_roads)
if __name__ == "__main__":
main()
算法及复杂度
- 算法:Dijkstra 算法
- 时间复杂度:
。主要开销是使用优先队列的 Dijkstra 算法,图中有
个顶点和
条边。
- 空间复杂度:
,用于存储图的邻接表和 Dijkstra 算法所需的距离数组等。