ECNU1468:Bicriterial routing

——url:http://acm.cs.ecnu.edu.cn/problem.php?problemid=1468

——problem:路径A比路径B时间少"且"费用低才算A比B"好", 所以B最好只是意味着没有别的路径比它好, 而不是B比别的路径都好. 听起来很拗口, 因为本题的目标函数值不具有全序关系, 即存在不可比较的情况. 所以时间4费用5和时间5费用4的两条路径无法比较. 如果没有比它们更好的, 则它们都是最优"双调路径"(bicriterial应该是双重准则的意思). 另外, 如果有两条时间4费用5的最优双调路径, 则在答案里只能计数一次

——solution:

转载自Answeror:

本题的Dijkstra定标有两种方法, 形象地说就是一维贪心和二维贪心. 注意上述优化对下面两种定标方法都是适用的.

方法1(一维贪心):

即传统的二维Dijkstra, 直接对时间贪心, 一个堆维护所有标号.

方法2(二维贪心):

即黑书上的方法, 初看起来是挺令人费解的, 但是它却对于Dijkstra定标技术的本质的理解很有帮助.

首先要看黑书P312页上例题4的最后一段话:

    事实上,这里的拓扑顺序已经不再来源于图中的边了(请读者对比有向无环图的拓扑顺序以及在动态规划中的应用),而是d值的大小。d是非负的,而且在计算过程中不减,这才是本题的算法乃至于更一般的最短路的标号设定算法可行的根本原因。

即Dijkstra的本质事实上是在为图建立一个拓扑顺序, 使得依照此拓扑顺序运用{CLRS}{P364}的DAG-SHORTEST-PATHS就可以求出任意的无负环图上的单源最短路. 而这个拓扑顺序来源于Dijkstra的贪心过程, 即在保证d在计算过程中不减(准确的说是永久化的标号对应的d值不会再减小, 也就是说跟s直连的边(出边)是可以为负的, 如果图中不存在负权回路的话)的前提下贪心地选择最小的d加入拓扑序列.

而黑书上这题的解法说"我们并不需要严格的拓扑顺序,而只需要一个让标号永久化的理由"个人感觉有些误导的意思, 我认为他的意思是想说, 本题并不需要按照对d贪心的拓扑顺序来做, 还有更加高效的拓扑顺序, 生成这个拓扑顺序的方法并不是依靠对d的贪心. 但是无论用什么方法, 拓扑顺序一定是跟标号(或称状态)的永久化(或称固定, 或者叫close)顺序是一致的.

考虑标号d[i,c]=t永久化的条件: 从其他永久标号得不到费用不大于c且时间不大于t的临时标号.

这样我们就可以不断地对所有具有当前最小费用c的标号进行定标, 黑书中"一次把多个临时标号同时变成永久的"也就是这个意思. 而对于具有当前最小费用c的标号进行定标也许要按照一定的顺序. 显然, 这时就需要按照时间从小到大来了. 所以这个方法生成的拓扑顺序是按照一个二维的贪心顺序进行的, 即先对费用贪心, 其次对时间贪心.

这个方法并没有像上述优化一样减少松弛次数(但是因为跟方法一的松弛顺序不同, 所以松弛(成功)次数可能会不同). 我们假设松弛顺序对松弛次数影响不大, 则在相同松弛次数的情况下, 这个方法应该更快. 因为Dijkstra的时间瓶颈在于松弛成功时的入堆操作, 方法2的入堆次数虽然没变, 但是堆的体积却变小了(因为按照费用拆分成了多个堆), 所以入堆时间也就减少了.

黑数上说需要两种堆我认为只要一个堆的数组维护对应于某个费用的所有时间(当然放进堆的元素还要包含顶点序号)即可. 因为费用可以从小到达迭代过去, 完全不需要堆来维护. 所以外层循环定费用, 内层循环定时间. 当然还可以先预处理出节点i的最短时间基础上的最小费用c[i], 就可以在某个永久化的标号扩展时剔除在i上费用大于c[i]的临时标号.

总结一下, 当而维Dijkstra的每一维标号(或者说状态)在计算过程中(注意是在计算过程中, 跟松弛操作中d减小是两码事)都只增不减, 则我们可以对每一维分别贪心, 用多个堆(第二维的长度个堆)维护标号. 如果Dijkstra到3维的话, 这种方法意义就不大了, 太繁琐.

代码1(使用方法二)

2397MS

View Code
1 #include<stdio.h>
2 #include<queue>
3 #include<memory.h>
4  using namespace std;
5  #define V 105
6  #define E 1000
7  #define MAX_C (n-1)*100
8  #define oo 0x7ffffff
9  int u, v, c, t, st, en, n, m, num, i, ans;
10 int head[V], nxt[E], ev[E], et[E], ec[E], dist[V][V * 100 + 1];
11 bool vis[V][V * 100 + 1];
12 struct node
13 {
14 int u, t;
15 friend bool operator <(node a, node b)
16 {
17 return a.t > b.t;
18 }
19 node(int a = 0, int b = 0) :
20 u(a), t(b)
21 {
22 }
23 } x, y;
24 void add_edge(int u, int v, int c, int t)
25 {
26 nxt[++num] = head[u];
27 head[u] = num;
28 ev[num] = v;
29 ec[num] = c;
30 et[num] = t;
31 }
32 int dijkstra()
33 {
34 int i, j, cost, temp = 0, min = oo;
35 priority_queue<node> q[MAX_C + 1];
36 memset(vis, 0, sizeof(vis));
37 for (i = 1; i <= n; i++)
38 for (j = 0; j <= MAX_C; j++)
39 dist[i][j] = oo;
40 dist[st][0] = 0;
41 q[0].push(node(st, 0));
42 for (i = 0; i <= MAX_C; i++)
43 while (!q[i].empty())
44 {
45 x = q[i].top();
46 q[i].pop();
47 if (vis[x.u][i])
48 continue;
49 vis[x.u][i] = true;
50 for (j = head[x.u]; j; j = nxt[j])
51 {
52 y.u = ev[j];
53 y.t = et[j] + x.t;
54 cost = i + ec[j];
55 if (cost <= MAX_C && y.t < dist[y.u][cost] && !vis[y.u][cost])
56 {
57 dist[y.u][cost] = y.t;
58 q[cost].push(y);
59 }
60 }
61 }
62 for (i = 0; i <= MAX_C; i++)
63 if (dist[en][i] < oo && min > dist[en][i])
64 {
65 min = dist[en][i];
66 temp++;
67 }
68 return temp;
69 }
70 int main()
71 {
72 num = 0;
73 memset(head, 0, sizeof(head));
74 memset(nxt, 0, sizeof(nxt));
75 scanf("%d%d%d%d", &n, &m, &st, &en);
76 for (i = 0; i < m; i++)
77 {
78 scanf("%d%d%d%d", &u, &v, &c, &t);
79 add_edge(u, v, c, t);
80 add_edge(v, u, c, t);
81 }
82 ans = dijkstra();
83 printf("%d\n", ans);
84 return 0;
85 }

代码2(使用方法一+优化)

0MS

优化:

f[i,j]表示到第i点花费j的最少时间。

由于dijstra是每次提取最小的,堆顶元素的时间必然不下降。

假如有一次堆顶是f[i,j1],提取扩展后,之后有一次堆顶是f[i,j2]

只有要 j2<j1的时候才可能成为双调路径,因为f[i,j1]必然小于f[i,j2]。

所以我们对于每次提取i后  用minc[i]记录已经扩展过的i节点的最小花费。

该优化不能用于方法二,原因在于方法二的费用T不一定是不下降的。

View Code
1 #include<stdio.h>
2 #include<queue>
3 #include<memory.h>
4 using namespace std;
5 #define V 105
6 #define E 1000
7 #define MAX_C (n-1)*100
8 #define oo 0x7ffffff
9 int u, v, c, t, st, en, n, m, num, i, ans;
10 int head[V], nxt[E], ev[E], et[E], ec[E], dist[V][V * 100 + 1], minc[V];
11 bool vis[V][V * 100 + 1];
12 struct node
13 {
14 int u, t, c;
15 friend bool operator <(node a, node b)
16 {
17 return a.t > b.t;
18 }
19 node(int a = 0, int b = 0, int c = 0) :
20 u(a), t(b), c(c)
21 {
22 }
23 } x, y;
24 void add_edge(int u, int v, int c, int t)
25 {
26 nxt[++num] = head[u];
27 head[u] = num;
28 ev[num] = v;
29 ec[num] = c;
30 et[num] = t;
31 }
32 int dijkstra()
33 {
34 int i, j, cost, temp = 0, min = oo;
35 priority_queue<node> q;
36 memset(vis, 0, sizeof(vis));
37 for (i = 1; i <= n; i++)
38 for (j = 0; j <= MAX_C; j++)
39 dist[i][j] = oo;
40 for (i = 0; i <= n; i++)
41 minc[i] = oo;
42 dist[st][0] = 0;
43 q.push(node(st, 0, 0));
44 while (!q.empty())
45 {
46 x = q.top();
47 q.pop();
48 if (vis[x.u][x.c])
49 continue;
50 vis[x.u][x.c] = true;
51 if (minc[x.u] < x.c)
52 continue;
53 minc[x.u] = x.c;
54 for (j = head[x.u]; j; j = nxt[j])
55 {
56 y.u = ev[j];
57 y.t = et[j] + x.t;
58 y.c = x.c + ec[j];
59 if (y.c <= MAX_C && y.t < dist[y.u][y.c] && !vis[y.u][y.c])
60 {
61 dist[y.u][y.c] = y.t;
62 q.push(y);
63 }
64 }
65 }
66 for (i = 0; i <= MAX_C; i++)
67 if (dist[en][i] < oo && min > dist[en][i])
68 {
69 min = dist[en][i];
70 temp++;
71 }
72 return temp;
73 }
74 int main()
75 {
76 num = 0;
77 memset(head, 0, sizeof(head));
78 memset(nxt, 0, sizeof(nxt));
79 scanf("%d%d%d%d", &n, &m, &st, &en);
80 for (i = 0; i < m; i++)
81 {
82 scanf("%d%d%d%d", &u, &v, &c, &t);
83 add_edge(u, v, c, t);
84 add_edge(v, u, c, t);
85 }
86 ans = dijkstra();
87 printf("%d\n", ans);
88 return 0;
89 }
posted on 2011-03-27 18:58  风也轻云也淡  阅读(539)  评论(0编辑  收藏  举报