使用C++定向越野地图游戏

Orienteering map game using C++

本文关键字:地图 游戏 C++ 使用      更新时间:2023-10-16

我使用蛮力方法解决了编程难题,没有动态编程,它工作得很好。 这是谜题:

定向越野地图将按以下格式给出。

" ##### "
" #...# "
" #S#G# "
" ##### "

通过所有检查点计算从起点到目标的最小距离。

  • 地图由 5 个字符组成,如下所示。您可以假设地图不包含任何无效字符,并且地图只有一个起始符号"S"和一个目标符号"G"。
  • "S"表示定向越野开始。
  • "G"表示定向越野目标。
  • "@"表示定向越野检查站。
  • "."表示玩家可以通过的开放方块。
  • "#"表示玩家无法通过的封闭式阻止。
  • 它只允许垂直或水平(向上、向下、向左或向右(移动一步到下一个块。其他类型的移动,例如对角线移动(左上、右上、左下和右下(和跳过一个或多个块,是不允许的。
  • 您一定不能离开地图。
  • 距离应定义为到不同块的移动次数。
  • 如有必要,您可以多次通过打开的块、检查点、起点和目标。
  • 您可以假设参数满足以下条件。
  • 1 <= 宽度 <= 100
  • 1 <= 高度 <= 100 最大检查点数为 18。

然后我找到了一个更快的解决方案,我不明白一些事情:

#include<iostream>  
#include<algorithm>  
#include<cstdio>  
#include<vector>  
#include<cstring>  
#include<map>  
#include<queue>  
#include<stack>  
#include<string>  
#include<cstdlib>  
#include<ctime>  
#include<set>  
#include<math.h>  
using namespace std;  
typedef long long LL;  
const int maxn = 1e2+ 10;  
#define rep(i,a,b) for(int i=(a);i<=(b);i++)  
#define pb push_back  
std::vector<int>path;  
const int INF=1<<20;  
struct Point  
{  
 int x,y;  
 bool operator < (const Point &a)const  
 {  
   return x<a.x||(x==a.x)&&y<a.y;  
 }  
};  
std::vector<Point>P;  
char mat[maxn][maxn];  
int vis[maxn][maxn];  
int w,h,s,e;  
int d[1<<20][20];  
int dx[]={-1,0,0,1};  
int dy[]={0,-1,1,0};  
int dist[25][25];  
int main(){  
ios_base::sync_with_stdio(false);  
cin.tie(0);  
while(cin>>w>>h){  
map<Point,int>id;  
P.clear();  
path.clear();  
memset(d,100,sizeof d);  
memset(dist,100,sizeof dist);  
for(int i=0;i<h;i++){  
    scanf("%s",mat[i]);  
    for(int j=0;mat[i][j];++j){  
        char &c=mat[i][j];  
        if(c=='S'||c=='G'||c=='@'){  
            P.pb((Point){i,j});  
            int sz=P.size();  
            id[P[sz-1]]=sz;  
            if(c=='S')s=sz-1;  
            else if(c=='G')e=sz-1;  
            path.pb(sz-1);  
        }  
    }  
}  
for(int i=0;i<path.size();i++){  
    Point now=P[path[i]];  
    int x=path[i];  
    //out<<"x "<<x<<endl;  
    dist[x][x]=0;  
    memset(vis,0,sizeof vis);  
    vis[now.x][now.y]=1;  
    queue<Point>q;  
    q.push(now);  
    //cout<<"Bfs"<<endl;  
    while(!q.empty()){  
        now=q.front();q.pop();  
        for(int i=0;i<4;i++){  
            int nx=now.x+dx[i],ny=now.y+dy[i];  
            if(nx>=0&&nx<h&&ny>=0&&ny<w&&mat[nx][ny]!='#'&&!vis[nx][ny]){  
                Point tp=(Point){nx,ny};  
                q.push(tp);  
                vis[nx][ny]=vis[now.x][now.y]+1;  
                if(id[tp]){  
                    dist[x][id[tp]-1]=vis[now.x][now.y];    
                }  
            }  
        }  
    }  
}  
d[1<<s][s]=0;  
int M=path.size();  
for(int i=0;i<(1<<M);++i){  
    for(int j=0;j<M;j++){  
        int p=path[j];  
        for(int k=0;1<<k<=i;k++){  
            if(i&(1<<k)){  
                d[i|(1<<p)][p]=min(d[i|(1<<p)][p],d[i][k]+dist[k][p]);  
            }  
        }  
    }  
}  
cout<<d[(1<<M)-1][e]<<endl;  
}  
return 0;  
}

以下是我对此的 3 个具体问题:

  1. 常数INF有什么用?它不会在程序的任何地方使用。我知道程序员经常在他们的程序中留下一些东西,这些东西目前似乎没有任何用处,但对未来的任何修改都是有用的。INF是否具有相同的目的?如果执行任何类型的修改以使程序更有效或使用不同的方法,则使用INF

  2. 在数组维度中使用左移运算符。例如,int d[1<<20][20] .让移运算符在这个程序中有什么目的?还有其他各种实例在数组维度中使用了 let shift 运算符,我不明白为什么。

  3. 小于运算符的重载。在Point结构中,小于运算符过载。但我似乎找不到它在程序中的哪个位置被调用。它需要一个 Point 对象来调用它,但我找不到任何 Point 对象调用该成员函数的地方。

您的问题不是无效的,但不需要所有上下文来提问。 它们都可以是单独的问题,我为每个问题提供了一个链接,表明问题的本质之前已经更简洁地提出过。 如果你隔离你的问题,并将它们从你正在查看的特定代码主体中分离出来,那就更好了——它们可以更容易地被分类为重复项。

常量 INF 有什么用?它不会在程序的任何地方使用。我知道程序员经常在他们的程序中留下一些东西,这些东西目前似乎没有任何用处,但对未来的任何修改都是有用的。INF是否具有相同的目的?如果执行任何类型的修改以使程序更高效或使用其他方法,则使用 INF?

如果删除声明 INF 的行,它是否仍可编译和工作? 会变慢吗? 如果是这样,那是一种神奇的咒语,可以使程序更快,只有在C++秘密社团中才知道。 :-) 如果没有,这只是您怀疑的剩余定义......也许在某个时候使用过,或者也许从未使用过。

看:

如何检测未使用的宏定义和类型定义?

在数组维度中使用左移运算符。例如,int d[1<<20][20]。让移运算符在这个程序中有什么目的?还有其他各种实例在数组维度中使用了 let shift 运算符,我不明白为什么。

在二进制数学中,将 1 向左移动一些位数与将 2 提高到该幂相同。 所以1 << 202^20,还是1048576。 位移比调用幂函数更快,尽管具有足够优化的幂函数,可以在基数为 2 时的特殊情况......快多少可能不是那么多:

2^n 指数计算真的比位移效率低吗?

小于运算符的重载。在 Point 结构中,小于运算符是重载的。但我似乎找不到它在程序中的哪个位置被调用。它需要一个 Point 对象来调用它,但我找不到任何 Point 对象调用该成员函数的地方。

有人可能会认为,如果你想测试一个方法是否被调用或使用过定义,你可以删除它,看看它是否仍然可以编译。 但在C++,这并不总是奏效;一些定义是重载。 如果删除它们,程序仍会编译,但只是进入更基本的行为。 即使是预处理器宏也可能很有趣,因为一个文件可能会检测到它是否在其他地方定义过,如果没有,则做一些不同的事情......

还有其他方法,例如只是抛出异常或断言它是否在运行过程中被调用。 人们在这里提供了一些其他的想法:

了解是否在C++项目中调用了函数?

正如@BrianSchlenker指出的那样,尽管所示代码中缺少显式调用,但肯定使用了小于运算符。 它用于对map<Point,int> id;的元素进行排序。 C++ std::map 类型对其内容施加排序,并默认使用 operator< 来实现此排序...尽管您可以覆盖它。 如果您在 less than 函数中打印出某些内容,则每次与 id 映射交互时,您都会看到它被调用。

(注意:如果你想要一个无序列图,你必须使用 std::unordered_map,但这要求你的数据类型具有不同的能力来计算其std::hash......以及对平等的测试。


通常:此代码不是以可维护或可读的方式进行样式化的。 我建议,如果你想学习提高C++程序性能的方法,你避免阅读你发现的任何一段混淆代码的麻烦......只是因为它碰巧引起了你的注意。

你能从中学习吗? 我想,但去混淆它并评论它将是您的第一步。 这不是一个好主意,特别是如果你必须去要求别人帮助你做到这一点,因为即使他们知道如何......他们可能不想。 更好的方法是通过步骤以稳定的逻辑方式改进您自己的实现,在任何一个步骤中,您都不会超出您的理解范围太远。

(不过,如果你能找到这些东西的原作者,你也许可以让他们参与到关于它的对话中,并为你发表评论。 如果他们没有兴趣,为什么互联网上的随机人会