题目大意
有 n 座城市和 m 条单向道路(两个城市之间可能有多条单向道路)。每次通过一条道路都要支付过路费(多次经过需要多次支付过路费),对于边 (ai,bi),过路费有两种支付方式:
- 如果之前到达过城市 ci,那么只需要支付 Pi块钱。(ci可能等于 ai)。
- 直接支付 Ri块钱。
问从 1 号城市到 n 号城市最少需要多少钱。
输入
第一行输入两个整数 n,m(1 ≤ n,m ≤ 10) 表示点数和边数。接下来 m 行每行五个整数 ai,bi,ci,Pi,Ri,描述了一条边。
输入数据保证 0 ≤ Pi ≤ Ri ≤ 100, 1 ≤ ai,bi,ci ≤ n.
输出
输出一行一个整数,表示最少的花费。如果不管怎么样都无法到达,输出impossible
大体思路
这道题与状压DP解TSP有异曲同工之妙。都是最短路径问题,因此我们不妨设一个数组dp[s][i]
:当前在点i
,已经经过了点集s
的最小花费,显然点集s
就是我们要压缩的对象。但是与TSP问题不同,我们可以可以经过某个点两次以上,因此不太适合迪杰斯特拉,使用SPFA即可。注意此时路径有两种花费,需要进行判断。spfa代码如下,
while (!q.empty()) {
int id = q.front(); q.pop(); vis[id] = 0;
for (unsigned i = 0; i < g[id].size(); i++) {//遍历所有邻接点
int to = g[id][i].to, by = g[id][i].advance;
int c1 = g[id][i].cost1, c2 = g[id][i].cost2;
for (int k = 1; k < (1 << n); k++) {//遍历所有状态
if (!(k&(1 << (id - 1)))) continue;//k状态连id点都不包含
int dist = ((1 << (by - 1))&k) ? c1 : c2;//经过了中间点就是cost2,否则就是正常花费
if (dist + dp[k][id] < dp[k | (1 << (to - 1))][to]) {
dp[k | (1 << (to - 1))][to] = dist + dp[k][id];
if (!vis[to]) {
q.push(to); vis[to] = 1;
}
}
}
}
}
我们来分析一下,为什么这样可以得到正确的结果,不妨使用样例
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
(结果为inf的update并不写出来)
queue:1
1st: update: dp[0001][1]=0 dp[0011][2]=10 dp[0101][3]=50
queue: 2,3
2ed: update: dp[0111][3]=dp[0011][2]+30=40 dp[0011][1]=dp[0011][2]+10=20
queue:3,1(注意这里,又回到了1,虽然到1的最短距离是0,但是这是不同的状态到的)
3rd: update: dp[1101][4]=dp[0101][3]+80=130 dp[1111][4]=dp[0111][3]+80=120
queue:1,4
4th: update: dp[0111][3]=dp[0011][1]+10=30(最优解的来源,1->2->1->3->4)
queue:4,3
5th: queue: 3
6th: update: dp[1111][4]=dp[0111][3]+80=110(√)
final res=min(dp[1001][4],dp[1011][4],dp[1101][4],dp[1111][4])(所有到4的路径的长度最小值)
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define MAX 15
#define inf 1000005
#define vec vector
struct edge {
int to, advance, cost1, cost2;
edge(int a = 0, int b = 0, int c = 0, int d = 0) {
to = a, advance = b, cost1 = c, cost2 = d;
}
};
int dp[1 << MAX][MAX], n, m, vis[MAX];
int main() {
cin >> n >> m;
vec< vec<edge> > g(MAX);
for (int i = 0; i < m; i++) {
int a, b, c, d, e; cin >> a >> b >> c >> d >> e;
g[a].push_back(edge(b, c, d, e));
}
//s:经过点点集至少有1
for (int i = 1; i < (1 << n); i++)for (int j = 1; j <= n; j++) dp[i][j] = inf;
dp[1][1] = 0; queue<int>q; q.push(1); vis[1] = 1;
while (!q.empty()) {
int id = q.front(); q.pop(); vis[id] = 0;
for (unsigned i = 0; i < g[id].size(); i++) {//遍历所有邻接点
int to = g[id][i].to, by = g[id][i].advance;
int c1 = g[id][i].cost1, c2 = g[id][i].cost2;
for (int k = 1; k < (1 << n); k++) {//遍历所有状态
if (!(k&(1 << (id - 1)))) continue;//k状态连id点都不包含
int dist = ((1 << (by - 1))&k) ? c1 : c2;//经过了中间点就是cost2,否则就是正常花费
if (dist + dp[k][id] < dp[k | (1 << (to - 1))][to]) {
dp[k | (1 << (to - 1))][to] = dist + dp[k][id];
if (!vis[to]) {
q.push(to); vis[to] = 1;
}
}
}
}
}
int res = inf, dst = (1 << (n - 1));
for (int i = dst; i < (1 << n); i++) {
if ((i & 1) && (i&dst)) res = min(res, dp[i][n]);
}
if(res!=inf)cout << res << endl;
else cout << "impossible" << endl;
}