剑之试炼|状压DP与多源最短路
2024-09-10 07:51:20
发布于:加拿大
87阅读
0回复
0点赞
第六题 - 剑之试炼
题目链接跳转:点击跳转
这道题超级恶心,我发自内心地对那些在比赛过程中 AC 此题目的同学表示尊敬。
思路上非常好想,由于所有的怪兽都要打,且打每一个怪兽的时间都是固定的,因此我们可以在一开始就先预处理出打完所有怪兽的时间,需要优化的就只有赶路的时间了。
先考虑每一层的情况,对于任意一层来说,关键点就是找到一个最优的路径(从某一个起点出发,经过所有的点后再传送到下一层的最短路径)即可。学习过状态压缩动态规划的同学可以很容易想到模板题【最短 Hamilton 路径】(洛谷链接:链接跳转)。
对于每一层来说,具体的实现方法和函数如下:
-
cover(dist, grid, "#*");
- 将
dist
和grid
中标记为"#"
或"*"
的所有障碍物进行覆盖处理。这一步的作用是标记出所有不可通行的区域,为后续的距离计算或路径规划提供基础。
- 将
-
getMinDist(dist, grid, "#");
- 计算从当前所在位置到
grid
中标记为"#"
(障碍物)位置的最短距离。这一步的作用是为后续的路径规划获取到障碍物的距离信息,方便下一步进行路径选择。
- 计算从当前所在位置到
-
hamilton(dist, grid);
- 在
dist
和grid
上执行哈密顿路径(Hamiltonian Path)的计算,目的是找到一条经过所有目标点的路径。这一步的作用是根据前面的距离信息,找到一条经过所有必经点的路径。
- 在
-
cover(dist, grid, "#.");
- 对
dist
和grid
中标记为"#"
(障碍物)和"."
(空地)的部分进行覆盖处理。这一步是为了确保后续的距离计算排除已标记的障碍物,并识别可通行的路径。
- 对
-
getMinDist(dist, grid, "#");
- 再次计算从当前所在位置到
grid
中标记为"#"
的最短距离。这一步是为了更新经过路径覆盖处理后的最短距离,确保得到最新的距离信息。
- 再次计算从当前所在位置到
除此之外就是一些小的细节优化了,具体请参阅代码注释。
本题的 AC 代码如下(本代码参考了黄老师的 std,并加以修改(我是不会说我数组传来传去把自己传死了,讨厌指针的一天)),若需要更详细的解答,可以 参考本文:
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
#include <vector>
using namespace std;
int n, m, k;
string map[30][105];
int dist[105][105];
struct node{
int x, y, w;
bool friend operator < (node a, node b){
return a.w > b.w;
}
};
const int dx[] = {1, 0, -1, 0};
const int dy[] = {0, 1, 0, -1};
/*
PS:本来想用纯数组加指针的方式做的。
后来写着写着把自己写死掉了,还是 vector 方便。
普通的二维数组传来传去简直要我的命。
::: 虽然我的编译器见不得 c++11 的标准,每次都给我提 warning。头大。
*/
// 转换,将每一个怪兽的血量都转换成对应的数字。
int calc(char c){
if (c == '.') return 0;
if (c == 'R') return 1;
if (c == 'B') return 3;
if (c == 'D') return 8;
if (c == 'S') return 24;
if (c == 'G') return 36;
return 0x7f7f7f7f;
}
// 最普通的广度优先搜索算法:
// 记录从 sx, sy 出发,到当前层 grid 的每一个点的最短路径。
// 其中 block 代表无法走的格子。
vector<vector<int> > bfs(vector<string> &grid, int sx, int sy, string block){
vector<vector<int> > dist(n, vector<int>(m, 0x7f7f7f7f));
dist[sx][sy] = 0;
struct node{
int x, y;
}; queue<node> que;
que.push((node){sx, sy});
while(!que.empty()){
node t = que.front();
que.pop();
for (int i=0; i<4; i++){
int cx = t.x + dx[i];
int cy = t.y + dy[i];
if (cx < 0 || cy < 0 || cx >= n || cy >= m) continue;
// 字符串函数,判断当前格子是否是障碍物,如果是障碍物的话就忽略。
if (block.find(grid[cx][cy]) != string::npos) continue;
if (dist[cx][cy] < 0x7f7f7f7f) continue;
dist[cx][cy] = dist[t.x][t.y] + 1;
que.push((node){cx, cy});
}
}
return dist;
}
// 将 dist 数组根据 grid 复原。
// 如果当前位置无法被走到,则将 dist 更新为无穷大。
void cover(vector<vector<int> > &dist, vector<string> &grid, string block){
for (int i=0; i<n; i++){
for (int j=0; j<m; j++){
if (block.find(grid[i][j]) != std::string::npos)
dist[i][j] = 0x7f7f7f7f;
}
}
return ;
}
// dijkstra 最短路算法。
// 主要作用是计算并更新每个节点到其余节点的最短距离,并通过广度优先搜索(BFS)算法来实现。
// dist[i][j] 是从一开始设定的起点到达 (i, j) 的累积代价,考虑了路径上经过的每个格子的代价。
void getMinDist(vector<vector<int> > &dist, vector<string> &grid, string block){
priority_queue<node> que;
// 一开始把所有起点都入队。
for (int i=0; i<n; i++){
for (int j=0; j<m; j++){
que.push((node){i, j, dist[i][j]});
}
}
while(!que.empty()){
node t = que.top();
que.pop();
if (dist[t.x][t.y] < t.w) continue;
for (int i=0; i<4; i++){
int cx = t.x + dx[i];
int cy = t.y + dy[i];
if (cx < 0 || cy < 0 || cx >= n || cy >= m) continue;
if (block.find(grid[cx][cy]) != string::npos) continue;
// 如果已经存在更优解了,就忽略。
if (dist[cx][cy] <= t.w + 1) continue;
dist[cx][cy] = t.w + 1;
que.push((node){cx, cy, t.w + 1});
}
}
}
// 计算汉密尔顿路径
// 即从起点出发走完所有的点最后再回来的路径最短路。
void hamilton(vector<vector<int> > &dist, vector<string> &grid){
struct node{
int x, y;
}; vector<node> vec;
for (int i=0; i<n; i++){
for (int j=0; j<m; j++){
if (grid[i][j] == '*')
vec.push_back((node){i, j});
}
}
int k = vec.size();
vector<vector<int> > f(k);
// f 数组用于计算每一个关键节点(怪兽)之间的最短路。
for (int i=0; i<k; i++){
int sx = vec[i].x;
int sy = vec[i].y;
auto toOther = bfs(grid, sx, sy, "#");
for (int j=0; j<k; j++){
f[i].push_back(toOther[vec[j].x][vec[j].y]);
}
}
// 对于 Hamilton 路径不熟悉的,直接去搜洛谷模板题先看一眼。
// X04-01 的同学应该是学过的(毕竟是我挑的题目)。
vector<vector<int> > dp(1 << k, vector<int>(k, 0x7f7f7f7f));
for (int i=0; i<k; i++){
int sx = vec[i].x;
int sy = vec[i].y;
dp[1 << i][i] = dist[sx][sy];
}
for (int i=0; i<(1<<k); i++){
for (int j=0; j<k; j++){
if (~i >> j & 1) continue;
for (int l=0; l<k; l++){
if (~(i ^ 1 << j) >> l & 1) continue;
dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][l] + f[l][j]);
}
}
}
for (int i=0; i<k; i++){
int sx = vec[i].x;
int sy = vec[i].y;
dist[sx][sy] = dp[(1 << k) - 1][i];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m >> k;
vector<vector<string> > grids(k, vector<string>(n));
for (auto &grid : grids){
for (auto &row : grid){
cin >> row;
}
}
int cost = k - 1;
for (auto &grid : grids){
for (auto &row : grid){
for (auto &c : row){
if (c != '#' && c != '.'){
cost += calc(c);
c = '*';
}
}
}
}
vector<vector<int> > dist = bfs(grids[0], 0, 0, "#*");
/*
先排除掉某些障碍物和关键点,以计算初步的最短路径。
再使用 hamilton 函数来处理关键点之间的路径规划,得到所有关键点的最优路径。
最后进一步排除空白区域和障碍物,重新计算网格中各点之间的最短路径,得到最终结果。
*/
for (auto &grid : grids){
cover(dist, grid, "#*");
getMinDist(dist, grid, "#");
hamilton(dist, grid);
cover(dist, grid, "#.");
getMinDist(dist, grid, "#");
}
// 计算答案。
int ans = 0x7f7f7f7f;
for (int i=0; i<n; i++){
for (int j=0; j<m; j++){
ans = min(ans, dist[i][j]);
}
}
cout << ans + cost << endl;
return 0;
}
这里空空如也
有帮助,赞一个