POJ 3411 Paid Roads 百炼 3148:样例分析+详细题解

题目链接:poj百炼

题目大意

有 n 座城市和 m 条单向道路(两个城市之间可能有多条单向道路)。每次通过一条道路都要支付过路费(多次经过需要多次支付过路费),对于边 (ai,bi),过路费有两种支付方式:

  1. 如果之前到达过城市 ci,那么只需要支付 Pi块钱。(ci可能等于 ai)。
  2. 直接支付 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值