HDU_2059 Race of rabbit and tortise(DP)

来源:互联网 发布:整形网络咨询技巧 编辑:程序博客网 时间:2024/06/06 23:15


龟兔赛跑

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 12223    Accepted Submission(s): 4584


Problem Description
据说在很久很久以前,可怜的兔子经历了人生中最大的打击——赛跑输给乌龟后,心中郁闷,发誓要报仇雪恨,于是躲进了杭州下沙某农业园卧薪尝胆潜心修炼,终于练成了绝技,能够毫不休息得以恒定的速度(VR m/s)一直跑。兔子一直想找机会好好得教训一下乌龟,以雪前耻。
最近正值HDU举办50周年校庆,社会各大名流齐聚下沙,兔子也趁此机会向乌龟发起挑战。虽然乌龟深知获胜希望不大,不过迫于舆论压力,只能接受挑战。
比赛是设在一条笔直的道路上,长度为L米,规则很简单,谁先到达终点谁就算获胜。
无奈乌龟自从上次获胜以后,成了名龟,被一些八卦杂志称为“动物界的刘翔”,广告不断,手头也有了不少积蓄。为了能够再赢兔子,乌龟不惜花下血本买了最先进的武器——“"小飞鸽"牌电动车。这辆车在有电的情况下能够以VT1 m/s的速度“飞驰”,可惜电池容量有限,每次充满电最多只能行驶C米的距离,以后就只能用脚来蹬了,乌龟用脚蹬时的速度为VT2 m/s。更过分的是,乌龟竟然在跑道上修建了很多很多(N个)的供电站,供自己给电动车充电。其中,每次充电需要花费T秒钟的时间。当然,乌龟经过一个充电站的时候可以选择去或不去充电。
比赛马上开始了,兔子和带着充满电的电动车的乌龟并列站在起跑线上。你的任务就是写个程序,判断乌龟用最佳的方案进军时,能不能赢了一直以恒定速度奔跑的兔子。
 

Input
本题目包含多组测试,请处理到文件结束。每个测试包括四行:
第一行是一个整数L代表跑道的总长度
第二行包含三个整数N,C,T,分别表示充电站的个数,电动车冲满电以后能行驶的距离以及每次充电所需要的时间
第三行也是三个整数VR,VT1,VT2,分别表示兔子跑步的速度,乌龟开电动车的速度,乌龟脚蹬电动车的速度
第四行包含了N(N<=100)个整数p1,p2...pn,分别表示各个充电站离跑道起点的距离,其中0<p1<p2<...<pn<L
其中每个数都在32位整型范围之内。
 

Output
当乌龟有可能赢的时候输出一行 “What a pity rabbit!"。否则输出一行"Good job,rabbit!";
题目数据保证不会出现乌龟和兔子同时到达的情况。
 

Sample Input
1003 20 55 8 210 40 601003 60 55 8 210 40 60
 

Sample Output
Good job,rabbit!What a pity rabbit!
 


题意:新龟兔赛跑,乌龟匀速,兔子骑电动车而且可以半路充电。给你各种参数,让你求出谁能通过最优方法获胜。

题解:这道题是很早的时候遗留下来的一道题。最开始看这道题的时候还不懂算法,只是单纯的逻辑推理,写出代码,看起来没错,但就是过不了。后来慢慢学了算法,知道自己原来用的算法叫贪心,而这道题得用DP来做,这才把这道遗留很久的题目A掉。

先讲一下原来错误的思路:

要取胜的决定性因素在于兔子经过每个加油点的时候要不要加油。有以下几种情况:

1.经过某点时剩余电量可以坚持跑到下个充点电,继续跑,否则跳至(2);

2.若充电的时间加上跑下面一段路程的时间>不充电跑完下面一段的时间,继续跑,否则跳至(3);

3.充电;

思路看起来是没有错的。但究竟为什么不能用贪心做呢?我们先证明贪心为什么是错的。

我知道不上样例你是不会死心的:

180
2 100 5
7 10 2
80 110

设想这样一种情况:总距离100,加油站在A:40、B:61,每次加满油能跑的距离为60.

贪心算法:到达A点,我们依据下一段距离21进行判断,只要(20/V1)+(1/V2) < (21/V1)+t, 我们就会选择不加油。到达B点,我们选择加油。T1=(100-1)/V1+1/V2+t;

DP算法:既然AB点必定会选择一点充电。我们选择A点。T2=100/V1+t;

所以贪心算法是错的。我们错在不应该仅根据下一段距离决定在该点是否充电。下面证明DP算法是对的。

对于每个点我们i,j(0<=j<i)从j到i有两种情况:

1.两者间距离大于l,开一段骑一段;

2.两者间距离小于l,直接开过去;

每次依据点i之前所有点的最优时间进行判断,得到点i的最优时间。

我们应该注意贪心与DP的区别,关键在于该问题本身阶段间状态的转移方式:

贪心:每个阶段的最优状态都是由上一个阶段的最优状态得到的;

DP:每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管这个状态是如何得到的;

知乎里这个回答很好,请点我

附代码:

//对间距操作        for(i=0;i<N;i++)        {            if(i==0)                a[i]=a1[i];            else                a[i]=a1[i]-a1[i-1];        }        a[N]=L-a1[N-1];        T1=L/VR;        //第一个点        if(C>=a[0])        {            T2+=a[0]/VT1;            left=C-a[0];        }        else        {            T2+=(C/VT1+(a[0]-C)/VT2);            left=0;        }        //剩余N个点,注意left更新        for(i=1;i<=N;i++)        {            //剩下的能跑完            if(left>=a[i])            {                T2+=(a[i]/VT1);                left-=a[i];            }            //跑不完            else            {                //剩下距离比满电距离还要长                if(a[i]>=C)                {                    //充电                    if(((left/VT1+(a[i]-left)/VT2))>((a[i]-C)/VT2+C/VT1+T))                    {                        T2+=((a[i]-C)/VT2+C/VT1+T);                        left=0;                    }                    else                    {                        T2+=(left/VT1+(a[i]-left)/VT2);                        left=0;                    }                }                else                {                    //充电                    if(((left/VT1+(a[i]-left)/VT2))>(a[i]/VT1+T))                    {                        T2+=(a[i]/VT1+T);                        left=C-a[i];                    }                    else                    {                        T2+=(left/VT1+(a[i]-left)/VT2);                        left=0;                    }                }            }        }

思路搞清楚代码实现起来就很简单了,我们只需要对点i之前的每个点j遍历(假设从该点充满电跑到点i),更新最短时间找到最优的充电方案:

附代码:

#include <stdio.h>#include <stdlib.h>#include <limits.h>#include <string.h>#define INF 0xfffffffint a1[105];
//到达每个点的最优时间double dp[105];int main(){    int L;    int N;    int C,T;    double VR,VT1,VT2;    int i,j,dis;    double Time;    double T1,T2;    while( scanf("%d",&L)!=EOF )    {        T1=0;T2=0;dis=0;        scanf("%d%d%d%lf%lf%lf",&N,&C,&T,&VR,&VT1,&VT2);        a1[0]=0;//将第一个点放入        for(i=1;i<=N;i++)            scanf("%d",&a1[i]);        a1[N+1] = L;//将最后一个点放入        for( i = 1; i <= N+1; i++ )        {            dp[i] = INF;//初始化为较大值            for( j = 0; j < i; j++ )            {                dis = a1[i] - a1[j];//净距离                Time = dis>C?(C/VT1+(dis-C)/VT2):dis/VT1;//计算时间                if( j != 0)//充点电充电                    Time += T;                //更新                dp[i] = dp[i]<dp[j]+Time?dp[i]:dp[j]+Time;            }        }        T1 = L/VR;        T2 = dp[N+1];        if(T1>T2)            printf("What a pity rabbit!\n");        else            printf("Good job,rabbit!\n");    }    return 0;}
0 0
原创粉丝点击