Bootstrap

贪心 模拟:CSP-J 2023 公路

原文链接:CSP-J真题第二讲:公路

本题有视频讲解,链接:CSP-J 2023 T2 公路_哔哩哔哩_bilibili

说明:CSDN和公众号文章同步发布,需要第一时间收到最新内容,请关注公众号【比特正传】。

上篇文章发布后,收到公众号后台私信,让我多写一些第2、3、4题的题解,读者如此合理的要求,必须满足。今天来看一篇2023年的普及组的第二题:公路。

一、题目背景

题目来源:CSP-J 2023年 T2

题目考察点:贪心,模拟

题目链接:

[CSP-J 2023] 公路 - 洛谷

题意:从站点1到站点n,车的油箱无限大(即可以装任意多油),每个站点油价各不相同,问从站点1到站点n的最小花费。

二、题目分析

做竞赛题呀,这个数据范围非常重要,关注一下数据规模【n\leqslant 10^{5}】,可以判断出这道题的复杂度不能超过O(n^{2}),O(n)或O(nlogn)复杂度比较合适,因此思考方向有了。

有两种方法解决,两种方法背后的本质是一样的,实现不一样,我们先看第一种,理解了第一种,更容易理解第二种。

方法一:

如果是你,你会选择在哪里加油?肯定是找便宜的站点加油。

在第一站的时候,只能在第一站加油,那么第一站加多少呢?可以很容易想到,加的油刚好满足我到达下一个比我更便宜的站点,因此找下一个比当前站点油价更便宜的站点,假设为站点x,然后当前站点加的油刚好满足我到站点x即可。

还有一点,因为每次加油必须加整数升油,因此到第x站点后,可能还会剩一点油,因此下一趟优先使用上一趟剩的油,不够了再加油。

AC code

#include "iostream"
#include "cmath"
using namespace std;
const int N = 1e5+7;
/**
 * 在某个站点处,找包含自己的前面所有站点油价最低的加即可
 **/
long long n, d, curDist, v;
long long ans = 0;  // 记录花费总和 
long long exist = 0;  // 记录到达当前站点时,之前加的油用剩下的还可以跑的距离 
int minCost; // 记录当前站点及之前的所有站点中,最低油价 
int dist[N], cost[N];
int main() {
	scanf("%d%d", &n, &d);
	for(int i=1; i<n; i++) scanf("%d", &dist[i]);
	for(int i=1; i<=n; i++) scanf("%d", &cost[i]);
	int i = 1;
	while(i < n) {
		int j = i;  // 双指针
		curDist = 0;
		while(j < n && cost[j] >= cost[i]) curDist += dist[j++];  // 找到下一个比第i站点更便宜的加油站 
		if(curDist <= exist) {  // 如果下一趟跑的距离,用上一趟剩余的油可以满足,那么本次就不用加油 
			exist -= curDist;
			i = j;
			continue;
		}
		curDist -= exist; // 先把之前剩余的油用了 
		v = curDist / d; // 计算本次要加的油量 
		if(curDist % d > 0) v++;  // 如果有零头,还需要多加一升 
		ans += v * cost[i];  // 计算费用 
		exist = v * d - curDist;  // 计算剩余的油量所能跑的距离数 
		i = j;   // 更新i 
	} 
	printf("%lld\n", ans);
	return 0;
}

方法二:

如果是你,你会选择在哪里加油?肯定是找便宜的站点加油,如果现在位于第x站点,如果需要加油,那么我们一定会选择包含x站点以前的所有站点中,油价最低的站点(因为油箱无限大,所以在当前站点,可以加前面某一站点的油,等价于在前面站点加油)

有两种方式找x站点之前的油价最低的站点:

方式一:暴力遍历1~x;

方式二:在遍历站点1~n的时候,用一个变量minCost记录当前的最低油价;即minCost=min(minCost, cost[i]);

很明显方式一的复杂度为O(n), 方式二的复杂度为O(1); 因此我们采用方式二寻找最低油价。

还有一点,因为每次加油必须加整数升油,因此可能从第x站点到x+1站点,还会剩一点油,需要记录下来,在下一段路程中,优先用上一段剩余的油跑。

我选择的解决思路是
在每个站点,只加恰好能够跑到下一站点的油,不会多加,但是一直记录当前站点及之前最便宜的站点的油价,比如目前已经到了第8站点,但是第8站点之前,第2站点的油价最便宜,保存在minCost中,那么在第8站点的时候,依旧会用第二站点的油价买油,这等价于在第2站点加了非常多的油,可以支撑车子跑到第8站点。

AC Code

#include "iostream"
#include "cmath"
using namespace std;
const int N = 1e5+7;
/**
 * 在某个站点处,找包含自己的前面所有站点油价最低的加即可
 **/
int n, d;
long long ans = 0;  // 记录花费总和 
long long exist = 0;  // 记录到达当前站点时,之前加的油用剩下的还可以跑的距离 
int minCost; // 记录当前站点及之前的所有站点中,最低油价 
int dist[N], cost[N];
int main() {
	scanf("%d%d", &n, &d);
	for(int i=1; i<n; i++) scanf("%d", &dist[i]);
	for(int i=1; i<=n; i++) scanf("%d", &cost[i]);
	int minCost = cost[1];  // 初始认为一号站点油价最低
	for(int i=1; i<n; i++) {     
		if(exist >= dist[i]) {
			exist -= dist[i];
			continue;
		}
		dist[i] -= exist;  // 用上一段剩下的油先抵消一部分下一段要跑的距离 
		minCost = min(minCost, cost[i]);  // 更新最低油价 
		int v = dist[i] / d;   // 计算加油量 
		if(dist[i] % d > 0) v++;  // 判断是否需要多加一升 
		exist = v * d - dist[i];  // 更新多余的量 
		ans += v * minCost;  // 更新总费用 
	}
	printf("%lld\n", ans);
	return 0;
}

;