1 条题解
-
0
题解:Paid Roads(P3411)
一、题目分析
本题要求找到从城市1到城市N的最小花费路径,其中部分道路有两种付费方式:
- 预付费:在城市支付
- 后付费:在到达城市后支付()
关键约束:
- 城市编号1~N,道路数量1~10
- 状态需记录已访问城市(用于判断是否可预付费)
- 需处理重边和环
二、解题思路
-
状态建模:
使用Dijkstra算法,状态为,其中:- :当前城市
- :已访问城市的二进制掩码(第位表示城市是否访问过)
-
预付费判断:
若包含,则可选择预付费,否则只能后付费。 -
路径存在性检查:
使用Floyd-Warshall算法预处理可达性矩阵,若城市1无法到达N则直接输出"impossible"。
三、代码解析
#include<stdio.h> #include<stdlib.h> #include<vector> #include<queue> using namespace std; const int INF=(1<<29); // 边结构:from->to,sit为预付费城市,dis1为预付费,dis2为后付费 struct edge { int from, to, sit, dis1, dis2; }; // 优先队列节点:距离、当前城市、访问掩码 struct heapnode { int di, num1, num2; bool operator< (const heapnode j) const { return di > j.di; } }; edge e[15]; int d[15][1100]; // 最小距离:d[u][mask] int con[15][15]; // 可达性矩阵 int use[15][1100]; // 访问标记 int m, n; void dijkstra(int s); void floyd_warshall(void); int main(void) { int i, j, u, p, ans; while(scanf("%d%d", &n, &m) == 2) { // 初始化可达性矩阵 for(i = 1; i <= n; i++) for(j = 1; j <= n; j++) con[i][j] = (i == j) ? 1 : 0; // 读入边并更新可达性 for(i = 1; i <= m; i++) { scanf("%d%d%d%d%d", &e[i].from, &e[i].to, &e[i].sit, &e[i].dis2, &e[i].dis1); con[e[i].from][e[i].to] = 1; } // 预处理可达性 floyd_warshall(); if(con[1][n] == 0) { printf("impossible\n"); } else { // Dijkstra计算最短路径 dijkstra(1); ans = INF; u = (1 << (n-1)); p = (1 << n); // 遍历所有可能的终态 for(j = 0; j < p; j++) { if(d[n][j|u|1] < ans) ans = d[n][j|u|1]; } printf("%d\n", ans); } } return 0; } // Dijkstra算法:带状态的最短路径 void dijkstra(int s) { int i, j, u, v, p; heapnode h; priority_queue<heapnode> heap; p = (1 << n); // 初始化距离数组和访问标记 for(i = 1; i <= n; i++) for(j = 0; j < p; j++) { d[i][j] = INF; use[i][j] = 0; } // 起点状态:距离0,城市s,掩码为仅包含s h.di = 0; h.num1 = s; h.num2 = (1 << (s-1)); d[s][1 << (s-1)] = 0; heap.push(h); while(!heap.empty()) { h = heap.top(); heap.pop(); u = h.num1; v = h.num2; if(!use[u][v]) { use[u][v] = 1; // 遍历所有边 for(i = 1; i <= m; i++) { if(e[i].from == u) { // 情况1:未访问预付费城市,只能后付费 if((((1 << (e[i].sit-1)) & v) == 0) && (d[u][v] + e[i].dis1 < d[e[i].to][v | (1 << (e[i].to-1))])) { d[e[i].to][v | (1 << (e[i].to-1))] = d[u][v] + e[i].dis1; h.di = d[u][v] + e[i].dis1; h.num1 = e[i].to; h.num2 = (v | (1 << (e[i].to-1))); heap.push(h); } // 情况2:已访问预付费城市,可选择预付费 else if((((1 << (e[i].sit-1)) & v) != 0) && (d[u][v] + e[i].dis2 < d[e[i].to][v | (1 << (e[i].to-1))])) { d[e[i].to][v | (1 << (e[i].to-1))] = d[u][v] + e[i].dis2; h.di = d[u][v] + e[i].dis2; h.num1 = e[i].to; h.num2 = (v | (1 << (e[i].to-1))); heap.push(h); } } } } } } // Floyd-Warshall算法:预处理可达性矩阵 void floyd_warshall(void) { int i, j, k; for(k = 1; k <= n; k++) for(i = 1; i <= n; i++) for(j = 1; j <= n; j++) con[i][j] = con[i][j] || (con[i][k] && con[k][j]); }
四、关键步骤解析
-
可达性预处理
使用Floyd-Warshall算法计算所有城市对的可达性矩阵,判断是否存在从1到N的路径。 -
状态初始化
- :到达城市且已访问城市集合为的最小花费
- 初始状态:,其余为
-
优先队列扩展
每次从堆中取出当前距离最小的状态,扩展所有出边:- 预付费条件:若包含,可选择
- 后付费条件:否则只能选择
- 更新新状态的距离
-
结果收集
遍历所有可能的终态(要求包含1和N),取最小值。
五、示例解析
对于输入:
4 5 1 2 1 10 10 2 3 1 30 50 3 4 3 80 80 2 1 2 10 10 1 3 2 10 50
-
路径分析
最优路径为:1 → 2 → 3 → 4- 1→2:预付费10(在1已访问)
- 2→3:预付费30(在1已访问)
- 3→4:预付费80(在3已访问)
总花费:10 + 30 + 80 = 110
-
状态转移
- 初始:
- 扩展到2:
- 扩展到3:
- 扩展到4:
但存在更优路径,最终答案为110。
六、注意事项
-
位运算处理
- 的第位表示城市是否访问(从0开始)
- 更新掩码:
-
状态空间
- 城市数,状态数为,可接受
-
优先队列优化
使用Dijkstra算法而非Bellman-Ford,避免重复扩展无效状态
七、复杂度分析
-
时间复杂度:
- Floyd-Warshall预处理:
- Dijkstra状态扩展:
-
空间复杂度:(距离数组和优先队列)
八、可能的优化
-
双向Dijkstra
从起点和终点同时搜索,可能减少状态扩展数。 -
状态剪枝
若发现某些状态不可能最优,提前剪枝。 -
预处理优化
对每个城市预处理可使用的预付费道路集合,减少状态转移时的判断。
- 1
信息
- ID
- 2412
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 4
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者