问题 B: 加油站(贪心+模拟)

加油站问题算法


Problem Link:http://139.129.36.234/problem.php?cid=1015&pid=1


问题 B: 加油站

时间限制: 1 Sec  内存限制: 128 MB
提交: 43  解决: 9
[ 提交][ 状态][ 讨论版]

题目描述

一辆汽车加满油后可行驶 n公里。 旅途中有若干加油站。 设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。请对于给定的 n和 k个加油站位置, 计算最少加油次数。

输入

对于输入数据,其第1行有2个正整数n(1≤n≤5000)和 k(1≤k≤5000)。表示汽车加满油后可行驶 n公里,且旅途中有 k个加油站。接下来的1行中,有 k+1个整数,表示第 k个加油站与第 k-1个加油站之间的距离。第 0个加油站表示出发地,汽车已加满油。第 k+1个加油站表示目的地。

输出

数据输出一行。 如果所对应的输入数据数据可以到达, 将计算的最少加油次数输出。 如果无法到达目的地,则输出“NoSolution”。

样例输入

7  7
1  2  3  4  5  1  6  6

样例输出

4

AC code:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<map>
#include<math.h>
#include<string.h>
#include<queue>
#include<vector>
#include<set>
#define LL long long
#define exp 1e-9
#define MAXN 1000010        
using namespace std;

int sta[5010];

int main( )  
{  
//	freopen("D:\\in.txt","r",stdin);
	int n,k,i,ans,cur;
	while(scanf("%d%d",&n,&k)!=EOF)
	{
		for(i=1;i<=k+1;i++)
		{
			scanf("%d",&sta[i]);	
		}
		cur=n;
		i=1;
		ans=0;
		if(cur<sta[1])
		{
			printf("NoSolution\n");
		}
		else
		{
			cur-=sta[1];
			i++;
			while(1)
			{
				if(sta[i]>cur)
				{
					ans++;
					cur=n;	
					if(sta[i]>cur)
					{
						printf("NoSolution\n");
						break;
					}
					else
					{
						cur-=sta[i];
						i++;
						if(i==k+2)
						{
							printf("%d\n",ans);
							break;
						}
					}
				}
				else
				{
					cur-=sta[i];
					i++;
					if(i==k+2)
					{
						printf("%d\n",ans);
						break;
					}
				}		
			}
		}	
	} 
	return 0;  
}  


<think>好的,我现在需要解决这个问题:使用贪心算法确定汽车在哪些加油站停靠加油,以使加油次数最少。首先,我得理解题目要求。输入是汽车的续航里程n,加油站数量k,以及k+1个距离,表示相邻加油站之间的距离。输出是最少加油次数,如果无法到达就输出“No Solution!”。首先,我应该考虑如何用贪心策略。贪心算法的核心是每一步都做出局部最优选择,希望导致全局最优。在这种情况下,当汽车行驶到无法到达下一个加油站时,应该在之前经过的加油站中油量最多的那个加油。这样能尽可能延长续航,减少加油次数。那具体步骤应该是怎样的?比如,从起点出发,油箱是满的。然后逐个检查每个加油站之间的距离。每当当前油量不足以到达下一个加油站时,就需要在之前经过的加油站中找一个最大的油量来加油。如果没有可加油的选项,说明无法到达。 这里可能需要一个优先队列(最大堆)来记录经过的加油站的可加油量。每次路过一个加油站时,将该加油站的油量加入优先队列。当油量不足以继续前进时,从优先队列中取出最大的油量进行加油,次数加一。如果队列为空,则无法到达。 举个例子,输入样例中的情况:n=7,加油站之间的距离是1、2、3、4、5、1、6、6。总共有7个加油站加上起点和终点,所以k=7,k+1=8段距离。需要输出4次加油。那如何模拟这个过程?初始时油箱有7单位的油。行驶第一段1,剩下6。第二段2,剩下4。第三段3,剩下1。第四段4,这时候当前油量1不够,所以需要加油。此时,已经经过的加油站有3个(假设每个加油站之间的距离对应一个加油站),那么在这三个加油站中是否有油?或者题目中的加油站是否每个都能加油?题目中的每个加油站都可以加油,但需要停靠才会加油。所以当车开到某个加油站时,可以选择是否加油。但根据贪心策略,应该尽可能在需要的时候选择最大的油量来加。 或者,可能题目中的每个加油站的油量是无限的,每次停靠可以加满油。或者题目中的加油站只允许加油一次?或者,这里的加油站之间的距离是固定的,而汽车只能在加油站加油,每次加油可以加满。题目中的汽车加满油可以行驶n公里。所以,当车到达一个加油站时,油箱里的油是剩下的,如果此时需要加油,可以加满,次数加一。哦,可能我理解错了问题。原题可能的意思是,汽车在出发时已经加满油。行驶过程中,经过各个加油站,如果无法到达下一个加油站,就需要在之前的某个加油站加油。但问题中的加油站是否只能停靠一次?或者是否可以在经过时选择是否加油?正确的贪心策略应该是:尽可能远地行驶,直到油量不足以到达下一个加油站时,才在之前经过的最大的加油站加油。这里可能需要维护一个最大堆,保存所有经过但未加油的加油站的可加油量(即每个加油站能加的油量是n,因为加满油)。或者,可能每个加油站只能加一次油,每次加油可以加满。所以,当车行驶到某个位置时,如果油不够到达下一个加油站,那么必须在已经经过的某个加油站加油,而选择其中能提供最大剩余油量的(即最远的那个加油站)。例如,每次尽可能行驶到最远,当无法继续时,回头找最大的加油站加油。或者,正确的做法是:每当车到达一个加油站时,检查剩余油量是否能到达下一个。如果不能,则在已经经过的加油站中选择一个来加油(加满),但为了最少次数,应该选择最大的可能,即最远的加油站。或者,这里可能需要使用优先队列,记录所有可以加油的站点,当需要加油时,选择油量最大的(即n,因为每次加满)。 例如,模拟过程:初始油箱:n=7。当前油量7。处理各段距离: 1.第一段距离1:油量7-1=6。可以到达第一个加油站,此时不加油,将7(该加油站可加的油量)存入堆中。 2.第二段距离2:需要油2。当前油6-2=4。到达第二个加油站,将7存入堆。3.第三段距离3:油4-3=1。到达第三个加油站,存入堆7。 4.第四段距离4:当前油1不够。需要加油。这时候堆中有三个7。取出最大的,加一次油,次数变为1。此时油量变为7(加满),然后行驶4,油剩下3。此时到达第四个加油站,将7存入堆。5.第五段距离5:当前油3不够5。需要加油。堆中有三个7(第一、二、三)和第四个的7。取出最大的,次数加1(总2次)。油量7,行驶5,剩下2。到达第五加油站,存入7。6.第六段距离1:油2-1=1。到达第六加油站,存入7。7.第七段距离6:当前油1不够6。需要加油。堆中有之前的几个7。取出一个,次数3次。油7,行驶6,剩下1。到达第七加油站,存入7。 8.第八段距离6:当前油1不够6。需要加油。堆中有剩下的7。取出,次数4次。油7,行驶6,剩下1。到达终点。 这样总共有4次加油,符合样例输出。所以,正确的做法是:每当无法到达下一个加油站时,从之前所有经过的加油站中选择一个来加油(加满),并且选择尽可能大的加油量,也就是每次取最大的那个加油站加油。这可以通过优先队列(最大堆)来实现。每次经过一个加油站,就将该加油站的可用油量(即n)加入堆。当油不够时,从堆中取出最大的,加油次数加一,并更新当前油量。如果堆为空,则无法到达。 那具体实现步骤:1.初始化当前油量为n,加油次数0,堆为空。 2.遍历每个加油站之间的距离:a.如果当前油量 <距离,则必须加油:从堆中取出最大值,直到油量足够或者堆为空。每次取出一次,次数加一,油量增加该值(即加满油)。b.如果无法补充足够的油(堆为空且当前油量不够),返回无解。c.否则,减去这段距离的油耗,并将当前加油站的可用油量(n)加入堆。注意,这里有一个问题:每个加油站只能被使用一次吗?比如,当经过一个加油站时,是否可以将它的油量加入堆,然后在需要的时候多次取出?或者每个加油站的油量只能被使用一次? 根据问题描述,汽车在加油站停靠加油的次数应该是一次性的。例如,每个加油站一旦被选择加油,那么之后不能再重复使用。所以,当经过一个加油站时,将该加油站的可用油量(n)加入堆,之后如果需要加油,可以从中取出,但每个加油站只能被取出一次。因此,堆中的每个元素代表一个未被使用的加油站的可用油量。当取出后,不能再使用该加油站。因此,正确的算法是:-维护一个最大堆,保存所有已经经过但未使用的加油站的可用油量(n)。-当前油量初始为n(起点已经加满)。 -对于每个段距离d:-检查当前油量是否足够行驶d:如果不够,则需要从堆中取出最大的油量,直到足够或者堆空。-每次取出堆顶元素,加油次数加一,当前油量+=该油量(即加满油)。-如果堆为空且油量仍不足,返回无解。-行驶d距离,当前油量 -=d。-将该段对应的加油站的油量n加入堆(因为到达该加油站后,可以选择之后加油)。 不过,这里可能有一个问题:例如,每个加油站之间的距离是d_i,那么从起点到第一个加油站需要行驶d_0,这个d_0是第一个段。比如,输入样例中的第一个加油站到起点的距离是1,第二个距离是2,等等。所以,当处理到第i个段时,该段是起点到第i个加油站的距离吗?或者,原题中的输入是k+1个整数,表示第k个加油站与第k-1个之间的距离。例如,输入的第二行有k+1个数,对应的是从起点到第一个加油站的距离,第一个到第二个的距离,直到第k个到终点。例如,输入样例中的第二行是12345166,总共有7+1=8个距离,对应的是起点到第1个加油站的距离是1,第1到第2是2,依此类推,直到第7个加油站到终点的距离是6。总共有k=7个加油站,加上终点,总共有k+1段距离。所以,在处理每个距离d时,每处理完d之后,到达了一个新的加油站(或者终点)。例如,处理第一个d=1后,到达第一个加油站。处理第二个d=2后,到达第二个加油站,依此类推。最后一个d是到终点的距离。所以,在每次处理一个d时,当前的油量是否能到达下一个点?如果不能,需要从已经经过的加油站中选择加油。每个加油站对应的是处理到该段d之前的点。例如,处理d_i时,该段是到达第i个加油站的距离。所以,当处理d_i时,如果无法行驶d_i的距离,则需要从之前经过的加油站(0到i-1)中选一个加油。此时,每当处理完一个d_i之后,才到达第i个加油站。所以,在处理d_i时,需要先检查当前油量是否足够,如果不够,必须从已经经过的加油站中选择加油。而已经经过的加油站是0到i-1个。例如,处理d_i时,当前在加油站i-1的位置,需要行驶d_i到达加油站i。如果当前油量不足d_i,则需要从之前经过的加油站(0到i-1)中选择加油。但根据问题描述,每个加油站在被经过时,可以将其视为一个可加油的选项。例如,当到达加油站i时,可以在此后的任何时候选择在该加油站加油。但根据贪心策略,当需要加油时,应该选择能提供最多油的加油站,也就是最大的n,所以每次选择最大的可用加油站。因此,正确的做法是:-当前油量为current_fuel,初始为n(起点加满)。-最大堆保存所有经过的加油站的可加油量(即每个加油站可以加n)。 -遍历每个距离d:-如果current_fuel <d:-需要从堆中取出最大的加油站,加油,直到current_fuel >=d或者堆为空。-每次取出一个油量(即加一次油),次数加一,current_fuel +=该油量。-如果堆为空且current_fuel<d,输出无解。-行驶d距离:current_fuel -=d.-将当前加油站的n加入堆(因为处理完d之后到达该加油站,可以之后在此加油)。注意,终点不是加油站,所以最后一个d处理完后不需要加入堆。或者,最后一个d是到终点的距离,处理完后不需要加入堆。例如,输入样例中的各个距离:d列表是1,2,3,4,5,1,6,6。总共有8个d,对应从起点到第1加油站,第1到第2,直到第7到终点。处理第一个d=1:current_fuel=7 >=1 →行驶,current_fuel=6。然后将该加油站的n=7加入堆?或者,当处理d=1时,行驶之后到达第一个加油站,此时可以将该加油站的可用油量(n=7)加入堆。是的。所以在处理每个d之后,到达一个加油站(除了最后一个d到终点),所以应该将该加油站的可用油量加入堆。例如,处理d=1后到达第一个加油站,此时可以加油,所以将7加入堆。同理,处理d=2后到达第二个加油站,将7加入堆。依此类推,直到处理第k个d后到达第k个加油站,将7加入堆。最后一个d是到终点的,处理完后不需要加入堆。 所以,在代码中,对于每个d,处理完行驶之后,如果是第i个d(i从0到k),那么当i<k时,对应的加油站是i+1?或者需要明确索引关系。例如,总共有k+1个d,其中前k个d对应的是到达k个加油站,最后一个d是到达终点。例如,当k=7时,d的长度是8,其中前7个d对应的是到达7个加油站,第8个d是到达终点。所以,在遍历每个d时,除了最后一个d之外,其余的d处理后都需要将对应的加油站的n加入堆。例如:for (int i=0; i< k+1; i++) {d= distance[i];//处理dif(i<k) {heap.push(n);} }这样,每处理一个d(除了最后一个),到达一个加油站,将该加油站的n加入堆。这样,在输入样例中,处理前7个d时,每个处理后都加入堆,第8个d处理后不加入。这样,在代码中,遍历每个d,从0到k(因为k+1个d),其中i的范围是0到k。当i <k时,对应的加油站是第i个,处理后加入堆。最后一个i=k是到终点的d,不需要加入。这样,在代码中,循环应该处理每个d,共k+1次。每次处理时:-检查能否行驶d:如果current_fuel <d,则从堆中取油。-行驶d,current_fuel-=d. -如果不是最后一个d,将n加入堆。这样,就能正确模拟整个过程。 那现在编写代码:使用优先队列(最大堆)来保存可用的加油站油量。在C++中,优先队列默认是最大堆,所以可以用priority_queue<int>。初始化current_fuel =n,count=0,pq为空。 然后遍历每个d:for (int i=0; i<k+1; ++i){int d= distance[i];while(current_fuel<d&&!pq.empty()){current_fuel +=pq.top();pq.pop();count++;}if (current_fuel <d){//无法到达cout <<"NoSolution!"<< endl;return;}current_fuel-=d;if (i !=k){ //不是最后一个d,即不是到终点的,加入堆pq.push(n);}} 最后输出count. 这样是否正确?输入样例中的情况:n=7,k=7,d=[1,2,3,4,5,1,6,6].处理第一个d=1:current_fuel=7 >=1 →减去1,current_fuel=6。i=0<7→加入堆7.第二个d=2: current_fuel=6>=2→减去2,current_fuel=4.i=1 <7 →加入堆7.第三个d=3:current_fuel=4 >=3 →减3→1.i=2 <7 →加入堆7.第四个d=4: current_fuel=1<4→需要加油。堆中有三个7。取出一个,count=1→ current_fuel=1+7=8>=4→减4→4. i=3<7→加入堆7.第五个d=5:current_fuel=4 <5 →取出堆顶7→ count=2→ current_fuel=4+7=11>=5→减5→6. i=4<7→加入堆7.第六个d=1:current_fuel=6 >=1 →减1→5. i=5<7→加入堆7.第七个d=6:current_fuel=5 <6 →取出堆顶7 →count=3 →current_fuel=5+7=12 >=6 →减6 →6.i=6 <7 →加入堆7. 第八个d=6(i=7,等于k=7,不加入堆):current_fuel=6 >=6 →减6 →0.处理完毕,总次数是4,符合样例。所以代码是正确的。那现在编写这个代码。需要注意的是,输入的第二个行有k+1个整数,所以需要读取k+1个数。例如,当k=7时,第二行有8个数字。在代码中,输入部分: cin>>n>>k; vector<int>dist(k+1); for(inti=0;i <k+1;++i) {cin>> dist[i];} 然后按照上述逻辑处理。这样,整个代码的结构就清晰了。最后输出count即可。测试其他情况,比如某个d超过n,且无法通过加油到达的情况:例如,n=3,k=1,距离是4和1。起点到第一个加油站的距离是4,超过n=3。此时无法到达,输出No Solution!。另一个例子,n=5,k=2,距离是5,0,0。这种情况下,处理第一个d=5,current_fuel=5>=5→减为0,加入堆5。第二个d=0,没问题,加入堆5。第三个d=0,没问题。总次数0。所以代码正确。综上,代码的逻辑是正确的,使用贪心算法,每次需要加油时选择能提供最大油量的加油站,从而保证加油次数最少。</think>以下是使用贪心算法解决该问题的C++代码: ```cpp #include <iostream> #include <vector> #include <queue> using namespace std; int main() { int n, k; cin >> n >> k; vector<int> dist(k + 1); for (int i = 0; i < k + 1; ++i) { cin >> dist[i]; } int current_fuel = n; int count = 0; priority_queue<int> pq; for (int i = 0; i < k + 1; ++i) { int d = dist[i]; // 需要加油时,从堆中取最大油量 while (current_fuel < d && !pq.empty()) { current_fuel += pq.top(); pq.pop(); count++; } // 无法到达 if (current_fuel < d) { cout << "No Solution!" << endl; return 0; } current_fuel -= d; // 非终点时,将加油站加入堆 if (i != k) { pq.push(n); } } cout << count << endl; return 0; } ``` **解释**: 1. **贪心策略**:每次油量不足时,从已经过的加油站中选择能提供最大油量的加油站加油(通过最大堆实现)。 2. **堆的维护**:每经过一个加油站,将其可加油量存入堆中,确保后续需要时能快速获取最大值。 3. **终止条件**:若堆为空且油量仍不足,则输出无解。 4. **复杂度分析**:每个加油站最多入堆和出堆一次,时间复杂度为 O(k log k)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林下的码路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值