G - Repairing a Road

题目:Repairing a Road

题目大意:有一个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呢?因为这种情况就算出现了,也肯定有比这个更优的答案,不予证明,反正我觉得是这样(咦,这个我好像前面就有提到过……)。

 

 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 }

 

posted on 2013-08-25 08:52  SCNUACM  阅读(412)  评论(0编辑  收藏  举报

导航