G - 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 }