UVA 11883 Repairing a Road(最短路径+暴力枚举)

You live in a small town with R bidirectional roads connecting C crossings and you want to go from crossing 1 to crossing C as soon as possible. You can visit other crossings before arriving at crossing C, but it's not mandatory.

You have exactly one chance to ask your friend to repair exactly one existing road, from the time you leave crossing 1. If he repairs the i-th road for t units of time, the crossing time after that would be viai-t. It's not difficult to see that it takes vi units of time to cross that road if your friend doesn't repair it. You cannot start to cross the road when your friend is repairing it.

Input 

There will be at most 25 test cases. Each test case begins with two integers C and R ( 2C1001R500). Each of the next R lines contains two integers xiyi ( 1xiyiC) and two positive floating-point numbers vi and ai ( 1vi45, 1ai5), indicating that there is a bidirectional road connecting crossingxi and yi, with parameters vi and ai (see above). Each pair of crossings can be connected by at most one road. The input is terminated by a test case with C = R = 0, you should not process it.

Output 

For each test case, print the smallest time it takes to reach crossing C from crossing 1, rounded to 3 digits after decimal point. It's always possible to reach crossing C from crossing 1.

 

题目大意:有一个C个点R条无向边(这俩字母好别扭……),每条边有一个花费vi和一个ai(浮点数)。现在我们有一个人,可以花费t(你爱多大就多大)的时间该修一条道路,什么时候开始修随你,修到什么时候随你,不过修的时候此路不通。如果总共修了t时间,那么这条路的花费就会变成vi * ai ^ (-t)。我们要从1走到C,求最小花费(花费就是时间)。

思路:首先要修路,肯定是从一开始就开始修,因为看出修得越久该路的花费就越少(不要告诉我你看不出那个是单调递减的函数),所以修的时间越长越好,而且,总不会说,我从这条路走过去了,然后你再开始修,修了一段时间,我再走一次,因为这都是无向边,根据最短路的性质一条边我们是肯定不会走两遍的,就算第二遍时间减少了也好。

其次,如果我们要修(x, y)这条边,我们要从x走到y,我们可能要先在x等一段时间,然后再从x走到y。我们等了一段时间再过去,路的花费也就减少了,可能要比我们直接走过去花的时间要更少。(样例真是业界良心……)

然后怎么办了,我们这里只有500条边,所以可以枚举每一条边,然后我们通过这一条边(可能从x到y也可能从y到x因为边是双向的,实际上大概有方法判定不过我懒……)。比如经过一条边(x, y)的最短路径就是dis(1, x) + (t - dis(1, x)) + vi * ai ^ (-t) + dis(y, C),第二个是可能站在x等待的时间,从最优的角度考虑t肯定是不会小于dis(1, x)。

其中dis(a, b)代表从a走到b所需要的最小花费。我们可以从1开始做单源最短路径,再从C开始做单源最短路径。嗯?C只有100?果断floyd,这太好写了。

设f(t) = dis(1, x) + (t - dis(1, x)) + vi * ai ^ (-t) + dis(y, C),怎么令这个f(t)最大呢?哦,不对,最小……首先我们可以观察一下这个函数,咳咳,观察不出来,求导吧……f'(t) = 1 - ln(ai) * vi * ai ^ (-t),求这个函数的零点,很好,这是一个单调的函数,果断二分求解0点。下界很简单,就是dis(1, x),那么上界呢?上界我们可以定为当前答案ans(初始化为dis(1, C)),因为如果修的时间比这个还多就没有意义了。问题是,这个是区间求零点,很可能没有零点,怎么办?如果零点在dis(1, x)的左边,那么t取dis(1, x)就好。如果在上界右边呢?这个我没分析也直接取了dis(1, x),因为我觉得应该不会出现要取上界右边这么坑爹的情况,反正就算取了它也不会是答案了,随便怎么样都无所谓了。

其实这上面的做法有个BUG,就是我可能从1走到x的时候,经过了边(x, y),然后其实修理(x, y)的时候我们并不能走(x, y)。但是为什么会AC呢?因为这种情况就算出现了,也肯定有比这个更优的答案,不予证明,反正我觉得是这样(咦,这个我好像前面就有提到过……)。

 

代码(32MS):

 1 #include <cstdio>

 2 #include <cstring>

 3 #include <algorithm>

 4 #include <iostream>

 5 #include <cmath>

 6 using namespace std;

 7 

 8 const int MAXN = 110;

 9 const int MAXE = 1010;

10 const double EPS = 1e-6;

11 

12 inline int sgn(double x) {

13     if(fabs(x) < EPS) return 0;

14     return x > 0 ? 1 : -1;

15 }

16 

17 double fpai(double t, double v, double a) {

18     //t - v * pow(a, -t)

19     return 1 - log(a) * v * pow(a, - t);

20 }

21 

22 inline void update_min(double &a, const double &b) {

23     if(a > b) a = b;

24 }

25 

26 double mat[MAXN][MAXN];

27 int x[MAXE], y[MAXE];

28 double v[MAXE], a[MAXE];

29 int n, m;

30 

31 void floyd() {

32     for(int k = 1; k <= n; ++k)

33         for(int i = 1; i <= n; ++i)

34             for(int j = 1; j <= n; ++j) update_min(mat[i][j], mat[i][k] + mat[k][j]);

35 }

36 

37 double find_t(int i, int x, int y, double l, double r) {

38     double L = l, R = r;

39     while(R - L > EPS) {

40         double mid = (L + R) / 2;

41         //cout<<fpai(mid, v[i], a[i])<<endl;

42         if(fpai(mid, v[i], a[i]) > 0) R = mid;

43         else L = mid;

44     }

45     if(sgn(fpai(L, v[i], a[i])) != 0) return l;

46     return L;

47 }

48 

49 double solve() {

50     double t, ans = mat[1][n];

51     for(int i = 0; i < m; ++i) {

52         t = find_t(i, x[i], y[i], mat[1][x[i]], ans);

53         update_min(ans, t + v[i] * pow(a[i], -t) + mat[y[i]][n]);

54         t = find_t(i, y[i], x[i], mat[1][y[i]], ans);

55         update_min(ans, t + v[i] * pow(a[i], -t) + mat[x[i]][n]);

56     }

57     return ans;

58 }

59 

60 int main() {

61     while(scanf("%d%d", &n, &m) != EOF) {

62         if(n == 0 && m == 0) break;

63         for(int i = 1; i <= n; ++i) {

64             for(int j = 1; j <= n; ++j) mat[i][j] = 1e5;

65             mat[i][i] = 0;

66         }

67         for(int i = 0; i < m; ++i) {

68             int aa, bb; double cc;

69             scanf("%d%d%lf%lf", &aa, &bb, &cc, &a[i]);

70             x[i] = aa; y[i] = bb; v[i] = cc;

71             update_min(mat[aa][bb], cc);

72             update_min(mat[bb][aa], cc);

73         }

74         floyd();

75         printf("%.3f\n", solve());

76     }

77 }
View Code

 

你可能感兴趣的:(最短路径)