题意
一张无向图,每条边有权值,可以选择不超过 $k$ 条路使其权值变成0,求 $S$ 到 $T$ 的最短路。(同洛谷 P4568)
分析
首先,分层图最短路可以有效解决这种带有 「阶段性」的最短路,这是分层图最短路的模板题。
建立 $0~k $ 层相同的图,每层之间相邻的节点之间也用权值为0的边相连(具体操作见代码)。第 $k$ 层表示已经将 $k$ 条道路置为0。最终把每层的终点连向一个超级汇点。最短路就是从第 $0$ 层源点到超级汇点的最短路。
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; const int maxe = 1e7 + 10; //仔细算算 const int INF = 0X3f3f3f3f; struct Edge { int to, w, next; }edges[maxe]; int head[maxn], cnt; int n, m, S, T, K; void init() { memset(head, -1, sizeof(head)); cnt = 0; } inline void AddEdge(int a, int b, int w) { int id = ++cnt; edges[id].to = b; edges[id].w = w; edges[id].next = head[a]; head[a] = id; } struct Node { int u, d; //节点的编号与距离 bool operator < (const Node x) const { return d > x.d; } }; int vis[maxn], dis[maxn]; void Dijsktra(int s) { priority_queue<Node>q; memset(vis, 0, sizeof(vis)); memset(dis, INF, sizeof(dis)); dis[s] = 0; q.push(Node{s, dis[s]}); while(!q.empty()) { Node x = q.top();q.pop(); int u = x.u; if(vis[u]) continue; vis[u] = true; for(int i = head[u]; i != -1;i = edges[i].next) { int v = edges[i].to; int w = edges[i].w; if(!vis[v] && dis[u] + w < dis[v]) { dis[v] = dis[u] + w; q.push(Node{v, dis[v]}); } } } } int main() { init(); scanf("%d%d%d%d%d", &n, &m, &S, &T, &K); for(int i = 0;i < m;i++) { int a, b, w; scanf("%d%d%d", &a, &b, &w); AddEdge(a, b, w); AddEdge(b, a, w); for(int j = 1;j <= K;j++) { AddEdge(a+(j-1)*n, b+j*n, 0); AddEdge(b+(j-1)*n, a+j*n, 0); AddEdge(a+j*n, b+j*n, w); AddEdge(b+j*n, a+j*n, w); } } for(int i = 0;i < K;i++) AddEdge(T+i*n, T+(i+1)*n, 0); //将每层终点连接到超级汇点 Dijsktra(S); printf("%d\n", dis[T+K*n]); return 0; }
这题可以用分层图的思想来理解,但是可以用dp来写,只需对Dijsktra作一点修改,对 $dis$ 数组增加一维,$dis[i][j]$ 表示到第 $i$ 个点,用了 $j$ 次免费机会的最短路径长度。
分层图的方法比较直观,但是占用的内存较多。
#include<bits/stdc++.h> using namespace std; const int maxn = 1e3 + 10; const int maxe = 2e3 + 10; //仔细算算 const int maxk = 1e3 + 10; const int INF = 0X3f3f3f3f; struct Edge { int to, w, next; }edges[maxe]; int head[maxn], cnt; int n, m, S, T, K; void init() { memset(head, -1, sizeof(head)); cnt = 0; } inline void AddEdge(int a, int b, int w) { int id = ++cnt; edges[id].to = b; edges[id].w = w; edges[id].next = head[a]; head[a] = id; } struct Node { int u, d, times; //节点的编号、距离和已经走0通道的次数 bool operator < (const Node x) const { return d > x.d; } }; int vis[maxn][maxk], dis[maxn][maxk]; void Dijsktra(int s) { priority_queue<Node>q; memset(vis, 0, sizeof(vis)); memset(dis, INF, sizeof(dis)); dis[s][0] = 0; q.push(Node{s, dis[s][0], 0}); while(!q.empty()) { Node x = q.top();q.pop(); int u = x.u, times = x.times; if(vis[u][times]) continue; vis[u][times] = true; for(int i = head[u]; i != -1;i = edges[i].next) { int v = edges[i].to, w = edges[i].w; if(times < K && dis[u][times] < dis[v][times+1]) //走0通道 { dis[v][times+1] = dis[u][times]; q.push(Node{v, dis[v][times+1], times+1}); } if(dis[u][times] + w < dis[v][times]) //不走0通道 { dis[v][times] = dis[u][times] + w; q.push(Node{v, dis[v][times], times}); } } } } int main() { init(); scanf("%d%d%d%d%d", &n, &m, &S, &T, &K); for(int i = 0;i < m;i++) { int a, b, w; scanf("%d%d%d", &a, &b, &w); AddEdge(a, b, w); AddEdge(b, a, w); } Dijsktra(S); int ans = INF; for(int i = 0;i <= K;i++) ans = min(ans, dis[T][i]); printf("%d\n", ans); return 0; }
参考链接:
1. https://blog.csdn.net/SSL_ZYC/article/details/94959850
2. https://www.luogu.org/problemnew/solution/P4568?page=2