Repairing a Road

来源:互联网 发布:税友软件好用吗 编辑:程序博客网 时间:2024/05/20 05:56

原题描述:

You live in a small town with R bidirectional roads connecting C crossings and you want to go from crossing 1 to crossingC 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 thei-th road for t units of time, the crossing time after that would beviai-t. It's not difficult to see that it takesvi 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 (2<=C<=100, 1<=R<=500). Each of the next R lines contains two integers xi, yi (1<=xi,yi<=C) and two positive floating-point numbers vi andai (1<=vi<=20,1<=ai<=5), indicating that there is a bidirectional road connecting crossingxi and yi, with parameters vi andai (see above). Each pair of crossings can be connected by at most one road. The input is terminated by a test case withC=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 crossingC from crossing 1.

题目大意:

  

有一个C个点R条无向边的图,每条边有花费vi和一个ai。现在有一个人,可以花费t的时间该修一条道路,修的时候此路不通。如果总共修了t时间,那么这条路的花费就会变成 vi * ai ^ (-t)。要从1走到C,求最小花费。

思路:

    根据题意时间越长越好,其次,如果我们要修(x, y)这条边,我们要从x走到y,我们可能要先在x等一段时间,然后再从x走到y。我们等了一段时间再过去,路的花费也就减少了,可能要比我们直接走过去花的时间要更少。。比如经过一条边(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的数据不大,果断floyd。

设f(t) = dis(1, x) + (t - dis(1, x)) + vi * ai ^ (-t) + dis(y, C),看到这个函数我们只能先求导:f'(t) = 1 - ln(ai) *vi * ai ^ (-t),求这个函数的零点,这是一个单调的函数,果断二分求解0点。下界很简单,就是dis(1,x),那么上界呢?上界我们可以定为当前答案ans(初始化为dis(1, C)),因为如果修的时间比这个还多就没有意义了。问题是,这个是区间求零点,很可能没有零点,怎么办?如果零点在dis(1, x)的左边,那么t取dis(1, x)就好。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const double MAX=1e-6;
double rood[101][101];
int x[501], y[501];
double v[501], a[501];
double find(int i,intx,int y,double l,doubler)
{
    doublell=l,rr=r,mm;
    while(rr-ll>MAX)
    {
        doubleavg=(ll+rr)/2;
        mm=1-log(a[i])*v[i]*pow(a[i],-avg);
        if(mm>0) rr=avg;
        elsell=avg;
    }
    mm=1-log(a[i])*v[i]*pow(a[i],-ll);
    if(fabs(mm)<MAX)
        returnll;
    if(mm>0)
        returnl;
    returnll;
}
void money(int n,intm) 
{
    inti;
    doublet,ans=rood[1][n];
    for(i=0;i<m;i++) 
    {
        t = find(i,x[i],y[i],rood[1][x[i]],ans);
        if(ans>t+v[i]*pow(a[i],-t)+rood[y[i]][n])
            ans=t+v[i]*pow(a[i],-t)+rood[y[i]][n];
        t = find(i,y[i],x[i],rood[1][y[i]],ans);
        if(ans>t+v[i]*pow(a[i],-t)+rood[x[i]][n])
            ans=t+v[i]*pow(a[i],-t)+rood[x[i]][n];
    }
    printf("%.3f\n",ans);
}
int main()
{
    intn,m;
    while(cin>>n>>m && n && m)
    {
        inti,j,k;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
                rood[i][j]=100000;
            rood[i][i]=0;
        }
        for(i=0;i<m;i++)
        {
            scanf("%d%d%lf%lf",&x[i],&y[i],&v[i],&a[i]);
            if(rood[x[i]][y[i]]>v[i])
                rood[x[i]][y[i]]=v[i];
            if(rood[y[i]][x[i]]>v[i])
                rood[y[i]][x[i]]=v[i];
        }
        for(k=1;k<=n;k++)
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)
                    if(rood[i][j]>rood[i][k]+rood[k][j])
                        rood[i][j]=rood[i][k]+rood[k][j];
        money(n,m);
    }
    return0;

0 0