PAT-A1033 or codeup 2031 To Fill or Not to Fill (贪心)题解

 To Fill or Not to Fill

时间限制: 1 Sec  内存限制: 32 MB

题目描述

With highways available, driving a car from Hangzhou to any other city is easy. But since the tank capacity of a car is limited, we have to find gas stations on the way from time to time. Different gas station may give different price. You are asked to carefully design the cheapest route to go.

输入

Each input file contains one test case. For each case, the first line contains 4 positive numbers: Cmax (<= 100), the maximum capacity of the tank; D (<=30000), the distance between Hangzhou and the destination city; Davg (<=20), the average distance per unit gas that the car can run; and N (<= 500), the total number of gas stations. Then N lines follow, each contains a pair of non-negative numbers: Pi, the unit gas price, and Di (<=D), the distance between this station and Hangzhou, for i=1,...N. All the numbers in a line are separated by a space.

输出

For each test case, print the cheapest price in a line, accurate up to 2 decimal places. It is assumed that the tank is empty at the beginning. If it is impossible to reach the destination, print "The maximum travel distance = X" where X is the maximum possible distance the car can run, accurate up to 2 decimal places.

样例输入1

59 525 19 2
3.00 314
3.00 0

样例输出1

82.89

样例输入2

50 1300 12 8
6.00 1250
7.00 600
7.00 150
7.10 0
7.20 200
7.50 400
7.30 1000
6.85 300

样例输出2

749.17

样例输入3

50 1300 12 2
7.10 0
7.00 600

样例输出3

The maximum travel distance = 1200.00

  说实话,这道题一开始看的我一脸懵逼,贪心写的蛮少的,这种题目没得什么思路。一开始我想按单价排个序,结果发现好像不行,后来按照路程排序,但是一直没想到什么思路。

  据说,贪心的题目要过一遍测试用例才能知道,跟着题解过一遍测试用例大概清楚了思路:  

  首先就是正常读取,判断0位置是否有加油站,没有就跑不了,然后遍历每个加油站和前一个加油站之间的路程,在加满油的状态能否跑到,不能跑直接就输出。最后重要一点,最后一个加油站加满油能不能跑到终点,不然还是到不了终点去。

  遍历完都ok了我们就知道,车子可以到终点,但是怎么算钱最小呢?

  我们假设到达某一个车站A(一开始也是到达0位置的车站),我们不知道能加多少油,但我们知道,就算我们加满了油,跑的范围最大也只是distance = Cmax * Davg,所以我们可以遍历从当前车站开始,到最大范围内的所有加油站(并且位置不超过目的地距离),有几种情况:1,有一个加油站比现在在的便宜;2,有多个加油站比现在在的便宜;3,没有加油站比现在的便宜;4,没有加油站(由于经过上面的遍历,我们知道此刻我们在的加油站一定是最后一站,不然就会造成无法到达目的地,但与上面我们遍历完是否能到达矛盾,所以一定是最后一个);5,没有加油站比现在便宜但是在范围内到了终点。

  对于情况1,我们很容易通过贪心原则知道加油加到刚好到达该站即可,但对于情况2,我们不应该加油加到这几个里面最小的那一个,而是应该加油加到第一个比现在在的加油站A便宜的那一个,为什么是这样?因为如果你加油从本次加油站A开到第一个比本次小的加油站B后用完油,从B加油加到刚好从B后面的加油站开始,范围是B的位置 到 distance + B的位置中第一个比B小的加油站C,那么,肯定会比从A加油一直到C要便宜,有多个这种情况也是这样处理的。(情况2的计算方法适用于情况1)

  对于情况3,那么我们只能是把油加到满,开到范围内最小的油价的加油站D,虽然他还是比我们之前的位置贵,但是我们不加油跑不了,所以到达D后,我们在以到达车站为D时,遍历从D开始的范围的所有车站。这里提醒一下,为什么要加满,因为不加满在从A到达其最大范围内,要用到其他加油站的油,但是比A要贵,所以我们尽可能用A的油,至于为什么要在D加油,因为不加油的话就开不出A的最大范围,也就到不了目的地。

  对于情况4,我们只要判断,该站到终点要多少油,以及箱子里面还剩多少油算出本站到终点花的钱即可。

  对于情况5,我们直接从本次车站开到终点即可。(计算方法与情况4一样)

  在说明一点,不管是本次到达的站点是上次站点A的范围中单价第一个比A站便宜的B站(到B站后油为0),还是到范围内最便宜但是还是比A站贵的D站,剩余的油都不足以到达下一次在本次的加油站(B或者D)的范围内的第一个比本次加油站便宜的站E。因为如果是B的话,剩余0,肯定不行。如果是D,剩余的肯定不能走完以A为起点的范围,当下一个在D范围内比D便宜的第一个加油站E一定不在A的范围内,不然按照情况3就不会选到D了(要是E在A的范围内且比D便宜比A贵,我们按照情况3就会到E而不会到D了),所以到达下一个比本站单价便宜的加油站一定要在本站加油。

 AC代码:

 

#include <stdio.h>
#include <algorithm>
#include <cmath>
using namespace std;
#define eps 1e-8
struct Node
{
	double p;//单价
	double d;//位置
}dnode[505];
bool cmp(Node a, Node b)
{
	if(a.d != b.d)
		return a.d < b.d;
	else
		return a.p < b.p;
}
int main(void)
{
	double C, D, Davg;
	int n;
	while(scanf("%lf %lf %lf %d", &C, &D, &Davg, &n) == 4)
	{
		for(int i =  0; i < n; i++)
		{
			scanf("%lf %lf", &dnode[i].p, &dnode[i].d);
		}
		sort(dnode, dnode + n, cmp);//按位置排序 
		bool flag = true;//flag为是否需要在计算 
		if(abs(dnode[0].d) < eps)
		{ 
			for(int i = 1; i < n; i++)
			{
				if(C * Davg < dnode[i].d - dnode[i - 1].d)//如果加满也到不了下一站 
				{
					printf("The maximum travel distance = %.2f\n", dnode[i - 1].d + C * Davg);
					flag = false;//已经不需要计算了
					break;
				}
			}
			if(flag && C * Davg < D - dnode[n - 1].d)//如果站之间都能到达,但最后一站到不了目的地 
			{
				printf("The maximum travel distance = %.2f\n", dnode[n - 1].d + C * Davg);
				flag = false;//已经不需要计算了 
			} 
		}
		else//没有0位置的加油站 
		{
			printf("The maximum travel distance = 0.00\n");
			flag = false;//已经不需要计算了
		}
		double nowp = dnode[0].p, nowd = dnode[0].d, mon = 0.0, nowv = 0;//nowp本站的单价,nowd本站里始发站0的距离 
		//nowv本站剩余油,mon钱 
		int i = 1; 
		double distance = C * Davg; //从本站开始的加满油能跑到的最远距离,相当于本站的最大范围 
		int mini;//如果出现情况3,记录到最小的单价的位置 
		if(flag)
		{
			while(flag)
			{
				double minp = 1e9;//记录出现情况3时最小的单价 
				bool b_f = false;//是否不为情况3 
				//判断情况4,5,到了最后一个站(因为最后一个站本来是n - 1,但是跳出循环之前会i++,使得i == n)到终点的钱(即使最后一个站在终点也正确)
				//情况4,5不管哪一种都会因为i == n跳出后面的循环使得到达这里 
				if(i == n && distance + nowd - D > -eps)
				{
					mon += ((D - nowd) / Davg - nowv) * nowp;//最后一个站到终点的油减去原本剩余的油*单价 
					flag = false;
				}
				else
				{
					while(i < n && distance - (dnode[i].d - nowd) > -eps) //当站没有遍历完并且在该站的范围内 
					{
						if(dnode[i].p < nowp)//如果是有单价比该站A小的站B,直接开到B 
						{
							mon += ((dnode[i].d - nowd) / Davg - nowv) * nowp;//从A开到B花的油钱 
							nowv = 0;//油花完了的(对应情况2) 
							nowp = dnode[i].p;//更新单价 
							nowd = dnode[i].d;//更新位置 
							i++;//i指向B站的范围内的第一个站 
							b_f = true;//不属于情况3 
							break;
						}
						else
						{
							if(dnode[i].p < minp)//可能为情况3,找到最小,如果后面发现不属于情况3则把b_f置为true 
							{
								minp = dnode[i].p;
								mini = i;
							}
						}
						i++;
					}
					if(i < n && !b_f)//为情况3并且并不属于情况4,5 
					{
						i = mini + 1;//i指向下一站的范围内的第一个站 
						mon += (C - nowv) * nowp;//更新钱 
						nowp = minp;//更新油价 
						nowv = C;//更新油(由于在本站要加满,所以一开始为C) 
						nowv -= (dnode[mini].d - nowd) / Davg;//加满后开到下一站的剩余 
						nowd = dnode[mini].d;//更新位置 
					}
				}
			}
			printf("%.2lf\n", mon);
		}
	}
	return 0;
}
				

 

  

 

 
posted @ 2019-08-07 10:47  funforever  阅读(300)  评论(0编辑  收藏  举报