前段时间看了一个关于A* 算法的文档1,决定尝试用网格背景下的地图写一下dijkstra和A* 算法。另外受 A* 算法的启发,本人又思考了一种类似蚂蚁探路的寻路算法。
dijkstra
使用0代表障碍点,1代表可通点,由01组成的矩阵就是一张类似坦克大战的地图。相邻点在垂直方向距离设置为10,对角方向认为可直通,距离为14。
####实例代码
#include <iostream>
#include <fstream>
#include <cstdio>
#include <memory>
#include <set>
#include <cmath>
#include <queue>
#include <vector>
#define unit 10
#define INF 10000
#define flag 4
using namespace std;
const int line=23;
const int column=61;
int dis[line][column];
bool visited[line][column];
int map[line][column];
class TPoint{//数组的两个下标顺序与空间直角坐标系的几何图形正好是横纵颠倒的,坐标设置要注意
public:
int x;
int y;
// TPoint *next;
};
TPoint pre[line][column];
struct cmp{
bool operator()(TPoint a,TPoint b){
return dis[a.x][a.y]>dis[b.x][b.y];
}
};
int Distance_Judgement(int a[][column], int ori_x, int ori_y, int next_x, int next_y){
if(ori_x==next_x && abs(ori_y-next_y)==1)
return unit;
if(ori_y==next_y && abs(ori_x-next_x)==1)
return unit;
if(abs(ori_x-next_x)==1 && abs(ori_y-next_y)==1)
return 1.4*unit;//对角线走法路经长为根号2倍单位
}
void Init_TPoint(TPoint *dian){
dian->x = -1;
dian->y = -1;
}
void Dijkstra(TPoint ori, TPoint des){
TPoint u, v;
int i, j;
int next_x, next_y;
//初始化
priority_queue<TPoint,vector<TPoint>,cmp> Q;
memset(dis,INF,sizeof(dis));
memset(visited,0,sizeof(visited));
for(i=0; i<line; i++){
for(j=0; j<column; j++){
pre[i][j].x = -1;
}
}
for(i=ori.x+1; i>=ori.x-1; i--){//从一个网格点出发有8个方向
for(j=ori.y+1; j>=ori.y-1; j--){
if(i==ori.x && j==ori.y)
pre[i][j].x = -1;
else
pre[i][j] = ori;
}
}
Q.push(ori);
dis[ori.x][ori.y]= 0;
while(!Q.empty()){
u = Q.top();
Q.pop();
visited[u.x][u.y] = 1;
if(u.x==des.x && u.y==des.y)
break;
for(i=u.x+1; i>=u.x-1; i--){//从一个网格点出发有8个方向
for(j=u.y+1; j>=u.y-1; j--){
next_x = i;
next_y = j;
if(map[next_x][next_y]==0)continue;//绕过障碍物
if((next_x==u.x && next_y==u.y) || (next_x<0||next_x>=line) || (next_y<0||next_y>=column))continue;//地图边界限制
if(dis[next_x][next_y]==INF || dis[u.x][u.y] + Distance_Judgement(map, u.x, u.y, next_x, next_y) < dis[next_x][next_y]){
dis[next_x][next_y] = dis[u.x][u.y] + Distance_Judgement(map, u.x, u.y, next_x, next_y);
if(!visited[next_x][next_y]){
v.x = next_x;
v.y = next_y;
visited[next_x][next_y] = 1;
Q.push(v);
pre[next_x][next_y] = u;
//map[next_x][next_y] = flag; //搜索范围染色
}
}
}
}
}
}
void Print_Path(TPoint pre[][column], TPoint ori, TPoint des){
int k;
TPoint tmp = des;
TPoint *tmppre = new TPoint[line*column];
tmppre[0] = des;
for(k=1; k<line*column ; k++){
tmppre[k].x = -1;
}
k = 1;
for(int ii=0; ii<line; ii++){
for(int jj=0; jj<column; jj++){
if(pre[tmp.x][tmp.y].x!=-1 && !(tmp.x==ori.x && tmp.y==ori.y)){
tmppre[k] = pre[tmp.x][tmp.y];
tmp = pre[tmp.x][tmp.y];
k++;
}
else
break;
}
}
for(int i=line*column-1; i>=0; i--){
if(tmppre[i].x!=-1)
cout<< " "<< "("<< tmppre[i].x<< ","<< tmppre[i].y<< ")"<< "->";
}
cout<< "Bingo!"<< endl;
}
int main(){
int i, j;
TPoint ori, des;
ifstream fin("map.txt", ios::in);
if(!fin)return -1;
while(fin.good()){
for(i=0; i<line; i++)
for(j=0; j<column; j++)
fin>> map[i][j];
}
ori.x = 2;
ori.y = 5;
des.x = 21;
des.y = 60;
Dijkstra(ori, des);
cout<< "最短路径长为"<< dis[des.x][des.y]<< endl;
/*以下代码可生成文件显示dijkstra搜索范围
ofstream os("map_mirrir.txt",ios::app);
if (os)
{
for (i=0;i<line;i++)
{
for (j=0;j<column;j++)
os<< map[i][j]<<" ";
os<<endl;
}
}
else
cerr<<"error"<<endl;
*/
cout<< "路径为";
Print_Path(pre, ori, des);
return 0;
}
##BFS与A算法
个人感觉,A在dijkstra的基础上,使那个路径Update的过程更有指向性,即从当前点向周围8个方向探寻后,比较的不是dis(now)+(1 or √2),而是这个值再加上被探寻点到终点的预测值,作为下一个“当前点”的选择优先标准。而事实上,地图染色也显示,A算法比dijkstra的染色区域小很多,方向性明显。
而如果只留下预测值部分,不管起点-now(dijkstra)部分,就成为BFS(最佳优先搜索)。当遇到U型障碍时,纯BFS可能会因为走进死胡同而绕路(其实A如果预测值选不好或者地图太奇葩也可能工作的不好,没办法,速度快总要付出点什么)
####实例代码
#include <iostream>
#include <cmath>
#include <fstream>
#include <cstring>
#include <algorithm>
#define unit 10
#define INF 10000
#define flag 4
#define corrective_coe 1.4
#define buffer_size 1500
using namespace std;
class TPoint{
public:
int x;
int y;
};
const int line=23;
const int column=61;
int h[line][column];
int g[line][column];
int f[line][column];
TPoint pre[line][column];
int activity[line][column];
int map[line][column];
int jishu=0;
bool compare(TPoint a,TPoint b){
return f[a.x][a.y]<f[b.x][b.y];
}
TPoint Active_Queue[buffer_size];
void Write_File();
int Distance_Judgement(TPoint, int, int);
void Initia(){
memset(f, INF, sizeof(f));
memset(g, 0, sizeof(g));
memset(h, 0, sizeof(h));
memset(activity, -1, sizeof(activity));
for(int i=0; i<line; i++){
for(int j=0; j<column; j++){
pre[i][j].x = -1;
}
}
}
int H_Calcu(TPoint u, TPoint des){
int diff_x, diff_y;
diff_x = abs(u.x - des.x);
diff_y = abs(u.y - des.y);
int smaller = min(diff_x, diff_y);
float dis = diff_x + diff_y -2*smaller + corrective_coe*smaller;
//上面的式子是对直角路径的实际距离修正,考虑到斜着走的情况,有兴趣还可以加上可能障碍带来的惩罚因子
return int(unit*dis);
}
int F_Calcu(TPoint u, TPoint des){//因为g数组里的信息已经是基于起点的,所以不需要再传入起点
TPoint v;
activity[u.x][u.y] = 0;
for(int i=u.x+1; i>=u.x-1; i--){
for(int j=u.y+1; j>=u.y-1; j--){
if(i==u.x && j==u.y) continue;
if(map[i][j]==0 || activity[i][j]==0 || (i<0||i>=line) || (j<0||j>=line))
continue;
v.x = i; v.y = j;
h[i][j] = H_Calcu(v, des);
if(activity[i][j]==-1){
activity[i][j] = 1;
map[i][j] = flag;
g[i][j] = g[u.x][u.y]+Distance_Judgement(u, i, j);
f[i][j] = g[i][j] + h[i][j];
//f[i][j] = 0 + h[i][j]; //把上行换成这行就是纯BFS
Active_Queue[jishu] = v;
jishu++;
pre[i][j] = u;
//cout<< i<<","<< j<<"de"<< "f is"<< f[i][j]<< endl;
}
if(activity[i][j]==1){
if(g[i][j] > g[u.x][u.y]+Distance_Judgement(u, i, j)){
pre[i][j] = u;
g[i][j] = g[u.x][u.y]+Distance_Judgement(u, i, j);
f[i][j] = g[i][j] + h[i][j];
//f[i][j] = 0 + h[i][j]; //把上行换成这个就是纯BFS
//cout<< i<<","<< j<<"de"<< "f is"<< f[i][j]<< endl;
}
}
}
}
}
int Distance_Judgement(TPoint u, int v_x, int v_y){
if(u.x==v_x && abs(u.y-v_y)==1)
return unit;
if(u.y==v_y && abs(u.x-v_x)==1)
return unit;
if(abs(u.x-v_x)==1 && abs(u.y-v_y)==1)
return 1.4*unit;//对角线走法路经长为根号2倍单位
}
void Print_Path(TPoint pre[][column], TPoint ori, TPoint des){
int k;
TPoint tmp = des;
TPoint *tmppre = new TPoint[line*column];
tmppre[0] = des;
for(k=1; k<line*column ; k++){
tmppre[k].x = -1;
}
k = 1;
for(int ii=0; ii<line; ii++){
for(int jj=0; jj<column; jj++){
if(pre[tmp.x][tmp.y].x!=-1 && !(tmp.x==ori.x && tmp.y==ori.y)){
tmppre[k] = pre[tmp.x][tmp.y];
tmp = pre[tmp.x][tmp.y];
k++;
}
else
break;
}
}
for(int i=k-1; i>=0; i--){
if(tmppre[i].x!=-1)
cout<< " "<< "("<< tmppre[i].x<< ","<< tmppre[i].y<< ")"<< "->";
}
cout<< "Bingo!"<< endl;
}
void Write_File(){//可以将搜索范围和每点的f值打印到文件里
ofstream os("map_mirrir.txt");//没有ios::app参数则为覆盖型写入
if (os)
{
for (int i=0;i<line;i++)
{
for (int j=0;j<column;j++)
os<< map[i][j]<< " ";
os<<endl;
}
}
else
cerr<<"error"<<endl;
ofstream om("g_show.txt");//没有ios::app参数则为覆盖型写入
if (om)
{
for (int i=0;i<line;i++)
{
for (int j=0;j<column;j++){
if(f[i][j]>10000)
om << 0<<" ";
else
om<< f[i][j]<< " ";
}
om<<endl;
}
}
else
cerr<<"error"<<endl;
}
int main(){
int i, j;
TPoint ori, des, temp;
ifstream fin("map.txt", ios::in);
if(!fin)return -1;
while(fin.good()){
for(i=0; i<line; i++)
for(j=0; j<column; j++)
fin>> map[i][j];
}
ori.x = 2;
ori.y = 5;
des.x = 19;//本程序由于至今未发现的原因,搜索范围只能限制于最左侧的正方形,太TM奇怪了
des.y = 15;//这也导致终点纵坐标不能超过line,否则陷入死循环
Initia();
Active_Queue[jishu] = ori;
jishu++;//实现队列的自动往后排
//system("pause");
activity[ori.x][ori.y] = 1;
while(activity[des.x][des.y]!=1){
temp = Active_Queue[0];
F_Calcu(temp, des);
f[temp.x][temp.y] = INF;
sort(Active_Queue, Active_Queue+jishu+1, compare);//优先队列更方便,但它似乎不能即时调整,只好使用数组+每次重排的方案
//cout<< temp.x<<","<< temp.y<< " "<< h[temp.x][temp.y]<< " "<< g[temp.x][temp.y]<< endl;
//system("pause");
}
cout<< "路径为"<< endl;
Print_Path(pre, ori, des);
cout<< "距离为"<< g[des.x][des.y]<< endl;
}
##触角探路算法
A* 算法一个关键词是h[x],即当前点到终点的预测。在我的形象理解中,h[x]就像一个橡皮筋连在终点和当前点之间,而g[x]则是连在起点个当前点之间,两根橡皮筋的拉力使当前点的运动模式不会漫无目的(dijkstra)也不会走极端(BFS)。
那么,如果只保留h[x]这根橡皮筋,而让当前点具有一定的环境探测功能呢?
也就是说,当前点的大体方向是由终点的方位决定,但每走一步都会探测周围一定距离内的情况,如果发现即将遇上障碍,则根据障碍的大小形状,进行相应的局部规避。
###实例代码
#include <iostream>
#include <cstring>
#include <fstream>
#define dimension 4 //探测上下左右4个方向
#define whisker_length 3 //局部探查范围,该值选择的大小在后面说明
#define Leap_of_Faith 5 //这个信仰之跃参数在后面说明
#define change_x 0
#define change_y 1
#define unit 10
using namespace std;
class TPoint{
public:
int x;
int y;
};
const int line=23;
const int column=61;
const int up_direc=0;
const int down_direc=1;
const int left_direc=2;
const int right_direc=3;
int map[line][column];
TPoint pre[line][column];
TPoint u;
int steps=0;
bool *Barrier_Detector(TPoint u){
bool *if_passable = new bool[dimension];
memset(if_passable, 1, sizeof(if_passable));
if_passable[up_direc] = map[u.x-whisker_length][u.y];
if_passable[down_direc] = map[u.x+whisker_length][u.y];
if_passable[left_direc] = map[u.x][u.y-whisker_length];
if_passable[right_direc] = map[u.x][u.y+whisker_length];
return if_passable;//将4个方位的障碍情况放到一个数组里传出来
}
int Direction_Guide(TPoint u, TPoint des){
int *movement = new int[2];
int distance_x = des.x-u.x;
int distance_y = des.y-u.y;
Here_we_go://每走一步都要重新探查方位和探测周围,所以直接goto回来
bool *if_passable = Barrier_Detector(u);
distance_x = des.x-u.x;
distance_y = des.y-u.y;
if(distance_x>0) movement[change_x] = 1;
else if(distance_x<0) movement[change_x] = -1;
else movement[change_x] = 0;
if(distance_y>0) movement[change_y] = 1;
else if(distance_y<0) movement[change_y] = -1;
else movement[change_y] = 0;
system("pause");
/*
下面是4个if结构,每个if负责两个方向;每个if里有4个while,分别是0障碍,1障碍,2障碍的情况
*/
if(distance_x>0 && distance_y>=0){
while(if_passable[right_direc] && if_passable[down_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+movement[change_y];
goto Here_we_go;
}
while(!if_passable[right_direc] && if_passable[down_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+0;goto Here_we_go;
}
while(if_passable[right_direc] && !if_passable[down_direc]){
u.x = u.x + 0;
u.y = u.y + movement[change_y] + (movement[change_y]==0);//探测到底部障碍时采用右向躲避策略
cout<<u.x<<","<<u.y<<endl;
steps = steps + 0+movement[change_y]+(movement[change_y]==0);goto Here_we_go;
}
while(!if_passable[right_direc] && !if_passable[down_direc]){
u.x = u.x - movement[change_x]*Leap_of_Faith;//探测到 ┘型死角时采用向上躲避策略
u.y = u.y - 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x];goto Here_we_go;
}
}
if(distance_x<=0 && distance_y>0){
while(if_passable[right_direc] && if_passable[up_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+movement[change_y];
goto Here_we_go;
}
while(!if_passable[right_direc] && if_passable[up_direc]){
u.x = u.x + movement[change_x] - (movement[change_x]==0);//探测到右部障碍时采用向上躲避策略
u.y = u.y + 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+(movement[change_x]==0);
goto Here_we_go;
}
while(if_passable[right_direc] && !if_passable[up_direc]){
u.x = u.x + 0;
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_y];
goto Here_we_go;
}
while(!if_passable[right_direc] && !if_passable[up_direc]){
u.x = u.x - movement[change_x]*Leap_of_Faith;//探测到 ┐型死角时采用向下躲避策略
u.y = u.y - 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x];
goto Here_we_go;
}
}
if(distance_x<0 && distance_y<=0){
while(if_passable[left_direc] && if_passable[up_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+movement[change_y];
goto Here_we_go;
}
while(!if_passable[left_direc] && if_passable[up_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x];
goto Here_we_go;
}
while(if_passable[left_direc] && !if_passable[up_direc]){
u.x = u.x + 0;
u.y = u.y + movement[change_y] - (movement[change_y]==0);//探测到上部障碍时采用向左躲避策略
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_y]+(movement[change_y]==0);
goto Here_we_go;
}
while(!if_passable[left_direc] && !if_passable[up_direc]){
u.x = u.x - movement[change_x]*Leap_of_Faith;//探测到 ┌型死角时采用向下躲避策略
u.y = u.y - 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x];
goto Here_we_go;
}
}
if(distance_x>=0 && distance_y<0){
while(if_passable[left_direc] && if_passable[down_direc]){
u.x = u.x + movement[change_x];
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+movement[change_y];
goto Here_we_go;
}
while(!if_passable[left_direc] && if_passable[down_direc]){
u.x = u.x + movement[change_x] + (movement[change_x]==0);//探测到左部障碍时采用向下躲避策略
u.y = u.y + 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x]+(movement[change_x]==0);
goto Here_we_go;
}
while(if_passable[left_direc] && !if_passable[down_direc]){
u.x = u.x + 0;
u.y = u.y + movement[change_y];
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_y];
goto Here_we_go;
}
while(!if_passable[left_direc] && !if_passable[down_direc]){
u.x = u.x - movement[change_x]*Leap_of_Faith;//探测到 └ 型死角时采用向上躲避策略
u.y = u.y - 0;
cout<<u.x<<","<<u.y<<endl;
steps = steps + movement[change_x];
goto Here_we_go;
}
}
if(distance_x==0 && distance_y==0)
return steps;
}
void Initia(){
int i, j;
for(i=0; i<line; i++){
for(j=0; j<column; j++){
pre[i][j].x = -1;
}
}
ifstream fin("map.txt", ios::in);
while(fin.good()){
for(i=0; i<line; i++)
for(j=0; j<column; j++)
fin>> map[i][j];
}
}
void Print_Path(TPoint pre[][column], TPoint ori, TPoint des){//没用上,你要想打印路径可以自己完善
int k;
TPoint tmp = des;
TPoint *tmppre = new TPoint[line*column];
tmppre[0] = des;
for(k=1; k<line*column ; k++){
tmppre[k].x = -1;
}
k = 1;
for(int ii=0; ii<line; ii++){
for(int jj=0; jj<column; jj++){
if(pre[tmp.x][tmp.y].x!=-1 && !(tmp.x==ori.x && tmp.y==ori.y)){
tmppre[k] = pre[tmp.x][tmp.y];
tmp = pre[tmp.x][tmp.y];
k++;
}
else
break;
}
}
for(int i=k-1; i>=0; i--){
if(tmppre[i].x!=-1)
cout<< " "<< "("<< tmppre[i].x<< ","<< tmppre[i].y<< ")"<< "->";
}
cout<< "Bingo!"<< endl;
}
int main(){
TPoint des;
int dis;
Initia();
u.x = 2;
u.y = 5;
des.x = 21;
des.y = 60;
dis = Direction_Guide(u, des);
cout<< "路径总长为"<< dis*unit<< endl;
return 0;
}
Leap_ of_ Faith参数:该算法有一个问题,就是如果遇到稍微高点的拐角型障碍,目标点向上移动后下次探测的结果还是向下移动,于是就卡在里面了。于是在拐角型上移时多移动一些距离,也许就能从拐角里“跳出去”,Leap_ of_ Faith就是“跳”的大小。
***whisker_length***参数:探测范围大,则能减少在某些半包围结构中浪费的时间,但有些狭窄路径就探测不到了;反之亦然。
####地图文件
1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1
1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1
1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
23*61