做法一:最短路径dijkstra
首先明白几个定义:
- 往下或者往右走不耗费
- x/y 减小一次减去费用B
- 起点和终点均已确定
f[i][j][k]
代表到(i, j)
且还剩下k步可以走的最小花费- 加油耗费A
- 可以增付一个油库,耗费C
- 先考虑加不加油,
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靠剪枝硬是做出来了(大法师牛逼)
博客链接
学习一下叭!
关于这位大佬的代码的思考:
- 一开始余剩步数为
k + 1
是因为他写的是“到付”。也就是到了新的位置才消耗一个步数
解决办法就是更新状态的时候写rs - 1
就可以啦!- 这些剪枝看起来很不起眼,但是细细想来还是很巧妙啊!虽然时间是比不上最短路径的(四倍时间)
但是考场上实在想不出来的话能写一个大法师也不亏呢。稍微进行了修改,仅供参考哦。
#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; //好习惯
}