[题]汽车加油 —— YbtOJ-高效进阶2021-「图论」第3章【例题4】

做法一:最短路径dijkstra

P4009 汽车加油行驶问题

首先明白几个定义:

  1. 往下或者往右走不耗费
  2. x/y 减小一次减去费用B
  3. 起点和终点均已确定
  4. f[i][j][k]代表到(i, j)且还剩下k步可以走的最小花费
  5. 加油耗费A
  6. 可以增付一个油库,耗费C
  7. 先考虑加不加油,
    1.若是当前位置有油库,自然强制加油或者不加跑人。
    (需要)加油的话立马结束,因为加完之后k就不是k了,更新为K了,状态变了。
    2.若是油还是满的……这个状态还是要跑啊,所以判断条件加上k < K

QAQ好坑啊,因为是定义的结构体,我在入队的时候输少了一个k值也不会报错QWQ害我找错半天。

#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int N = 101;
int n, K, A, B, C, ans = 0x3f3f3f3f;
int mp[N][N], dist[N][N][11];
bool vis[N][N][11];

struct E { int x, y, k, dis; };

bool operator > (E a, E b) { return a.dis > b.dis; };

int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};

bool Nomap(int x, int y) { return 1 <= x && x <= n && 1 <= y && y <= n; }

void dj() {
	priority_queue < E, vector<E>, greater<E> > q;
	memset(dist, 0x3f, sizeof dist);
	dist[1][1][K] = 0;
	q.push({1, 1, K, 0});
	while(q.size()) {
		E u = q.top();
		q.pop();
		int x = u.x, y = u.y, k = u.k;
		if(vis[x][y][k]) continue;
		vis[x][y][k] = 1;
		if(mp[x][y] && k < K) {//有加油站哦,而且油没满吗?强制加油哦亲~ 
			if(dist[x][y][K] > dist[x][y][k] + A )//值得加?那快点加吧~ 
				q.push({x, y, K, dist[x][y][K] = dist[x][y][k] + A});
			continue;//加完了就走,因为加满了油k就是K了;不加会跑到下面的if语句里面.
		}
		else//还是说自愿加油呢亲~ 
			if(dist[x][y][K] > dist[x][y][k] + A + C) {
				q.push({x, y, K, dist[x][y][K] = dist[x][y][k] + A + C});//自愿建一个站来加油……不算是被搜到的,所以 
			}//似乎不用continue?1.不用强制加油,所以分两路,加或者不加 
		if(k > 0) {//不加油吗亲?还能走动吗亲?那快点滚吧。 
			for(int i = 0 ; i < 4; i ++) {
				int nx = x + dx[i], ny = y + dy[i], nk = k - 1;
				if(!Nomap(nx, ny)) continue;
				int w = (nx < x || ny < y) ? B : 0;
				if(dist[nx][ny][nk] > dist[x][y][k] + w)//可以到新地方了 
					q.push({nx, ny, nk, dist[nx][ny][nk] = dist[x][y][k] + w});
			}
		}
	}
}
int main() {
	scanf("%d%d%d%d%d", &n, &K, &A, &B, &C);
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
			scanf("%d", &mp[i][j]);
	dj();
	for(int i = 0; i <= K; i ++) 	
		ans = min(ans, dist[n][n][i]);
	printf("%d", ans);
	return 0;
}

做法二:DFS

对了,这里有个大佬用DFS靠剪枝硬是做出来了(大法师牛逼)
博客链接
学习一下叭!
关于这位大佬的代码的思考:

  1. 一开始余剩步数为k + 1是因为他写的是“到付”。也就是到了新的位置才消耗一个步数
    解决办法就是更新状态的时候写rs - 1就可以啦!
  2. 这些剪枝看起来很不起眼,但是细细想来还是很巧妙啊!虽然时间是比不上最短路径的(四倍时间)
    但是考场上实在想不出来的话能写一个大法师也不亏呢。

稍微进行了修改,仅供参考哦。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 101, MAX = 0x3f3f3f3f;
int mp[N][N], f[N][N][11], n, k, a, b, c, ans = MAX;
void dfs(int x, int y, int fy, int rs) {//横坐标,纵坐标,目前费用,剩余步数+1
    int add = 0;  //此步到下一步的费用
    //以下都是剪枝操作哦~--------------------------------------------------- 
    if (mp[x][y] == -1) return;  //边界
    if (rs < 0 || fy >= ans) return; //这里rs=0的话,就完成不了到付了哦 
//    rs--;//这里是到付哦!所以一开始的rs是k+1呢~ 
    for (int i = rs; i <= k; i++)  //一个剪枝操作:此坐剩余步数在更多的情况下,费用还比目前费用少,则可剪枝。
        if (f[x][y][i] <= fy)//一个状态只会被步数更大的更新.余剩步数多且费用更小,说明已经有更优解. 
            return; //显然 余剩步数多且费用更小 的更优,不是吗? 
    f[x][y][rs] = fy;  //更新此坐标剩rs步时最小费用(记忆化)
    if (x == n && y == n) {
        ans = min(ans, fy);  //更新答案
        return;
    }
    //然后是两种加油的情况 ---------------------------------------------
    if (mp[x][y] == 1) add = a, rs = k;  //强制加油
    if (rs == 0) add = a + c, rs = k;  //没油了才设站加油,这就是大法师! 
    //然后是向四周扩展一格--------------------------------------------- 
    dfs(x + 1, y, fy + add, rs - 1);
    dfs(x, y + 1, fy + add, rs - 1);
    dfs(x - 1, y, fy + add + b, rs - 1);
    dfs(x, y - 1, fy + add + b, rs - 1);  //扩展
}
int main() {
    memset(mp, -1, sizeof mp);
    memset(f, 0x3f, sizeof f);
    scanf("%d%d%d%d%d", &n, &k, &a, &b, &c);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) scanf("%d", &mp[i][j]);
    dfs(1, 1, 0, k);//记得k + 1哦亲~ 
    printf("%d", ans);
    return 0;  //好习惯
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值