关于马拦过河卒题解
焦祺 08-11-17
Time Limit : 3000/1000ms (Java/Other) Memory Limit : 65535/32768K (Java/Other)
Problem Description
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
Input
包括若干组数据。第一行一个数n表示数据的组数。
紧接着有n行,一行四个数据,分别表示B点坐标和马的坐标。
Output
对应每组输入数据,输出一个数据,表示所有的路径条数。
Sample Input
1
6 6 3 3
Sample Output
6
这题目看似是搜索题,于是我就按搜索题的一般方法入手
先用广搜,把没测试数据通过之后提交发现超时!
于是我对其进行进一步分析,发现有一两处可做稍微减枝的地方
减完之后再交再次TLE!
几次的修改未果,最后一次还出现了MLE!
即然出现了MTL,于是我便换成了DFS搜索的方法。
提交后发现还好,0KB的Memery,不过还是TLE了!
几次的修改依然未果,无耐又转战为双向广搜,但这题要求的计算出方法总数,基本上要遍历出所有情况,所以启发式搜索一般也不能很好的发挥其优势。
后来在队友的提醒下,想起了递归算法有保存中间值的方法。
之前还做过类似的总结,可惜太久没有用遗忘了,
所以不时的看下自己的总结还是很有必要的。
以下是详细的解题过程,并从中得出更好的递推算法解题!
广度优先搜索:(解题代码)TLE
#include <iostream>
#include<queue>
using namespace std;
struct Point
{
int x;
int y;
};
Point start,end,house,N,P;
void bfs();
int counter;
int dir[][2]={{1,0},{0,1}};
int housedir[][2] = {{1,2},{2,1},{-1,2},{2,-1},{-2,1},{1,-2},{-1,-2},{-2,-1}};
char g[16][16];
int main()
{
int i,j,t,tempx,tempy,flag;
start.x = start.y = 0;
scanf("%d",&t);
while (t--)
{
scanf("%d %d %d %d",&end.x,&end.y,&house.x,&house.y);
//-----------开始画图:
for (i=0;i<=end.x;i++)
for (j=0;j<=end.y;j++)
g[i][j] = '0';
g[house.x][house.y] = '1';
flag = 0;
for (i=0;i<8;i++)
{
tempx = house.x+housedir[i][0];
tempy = house.y+housedir[i][1];
if (tempx>=0 && tempx<=end.x && tempy>=0 && tempy<=end.y)
g[tempx][tempy] = '1'; //1为不可走
if ( (tempx == end.x && tempy == end.y))
{
flag == 1;
break;
}
}
if (flag == 1)
{
printf("0/n");
continue;
}
g[end.x][end.y] = '2';
counter = 0;
bfs();
printf("%d/n",counter);
}
return 0;
}
void bfs()
{
queue<Point> Q; //建立一个队列
N.x = start.x; N.y = start.y;
Q.push(N);
while( !Q.empty() )
{
N=Q.front(); //找队列中第一个数
Q.pop(); //弹出来
int i;
//----------------------开始搜:
for(i=0;i<2;i++)
{
int tx=N.x+dir[i][0];
int ty=N.y+dir[i][1];
if(tx>=0 && tx<=end.x && ty>=0 && ty<=end.y)
{
if(g[tx][ty] != '1') //如果有路
{ //将四个方向各点推入队列.
P.x=tx;
P.y=ty;
Q.push(P);
// printf("%d %d/n",tx,ty);
}
if (g[tx][ty] == '2')
{
counter++;
}
}
}
}
}
一般深度优先搜索:(解题代码)TLE,或有少许BUG
#include <iostream>
using namespace std;
struct Point
{
int x;
int y;
};
Point start,end,house,N,P;
int housedir[][2] = {{1,2},{2,1},{-1,2},{2,-1},{-2,1},{1,-2},{-1,-2},{-2,-1}};
int dir[][2]={{1,0},{0,1}};
char g[16][16];
int counter;
void dfs(int x,int y);
int main()
{
int i,j,t,tempx,tempy,flag;
start.x = start.y = 0;
scanf("%d",&t);
while (t--)
{
scanf("%d %d %d %d",&end.x,&end.y,&house.x,&house.y);
//-----------开始画图:
memset(g,NULL,sizeof(g));
g[house.x][house.y] = '1';
flag = 0;
for (i=0;i<8;i++)
{
tempx = house.x+housedir[i][0];
tempy = house.y+housedir[i][1];
if (tempx>=0 && tempx<=end.x && tempy>=0 && tempy<=end.y)
{
g[tempx][tempy] = '1'; //1为不可走
}
if ( (tempx == end.x && tempy == end.y))
{
flag == 1;
break;
}
}
if (flag == 1)
{
printf("0/n");
continue;
}
g[end.x][end.y] = '2';
counter = 0;
dfs(0,0);
printf("%d/n",counter);
}
return 0;
}
void dfs(int x,int y)
{
int i,tx,ty;
if (g[x][y] == '2')
{
counter++;
return;
}
for (i=0;i<2;i++)
{
tx = x + dir[i][0];
ty = y + dir[i][1];
if (tx>=0 && tx<=end.x && ty>=0 && ty<=end.y)
{
if (g[tx][ty] != '1')
{
dfs(tx,ty);
}
}
}
}
深度优先搜索(保存路径) AC
用深度优先搜索(保存路径)提交了有十几次之多,终于搞定
用了几个小时的时间,差点没有崩溃!!
从原来的TLE,MLE到最后的0MS,0KB,还是很有成就感的。
很早之前就知道有递归保存路径防TLE的方法
但很久没有用还把它给忘记了,现在才知道它的强大。
不过这道题用这种法方需要考虑的地方很多!
比较经典的地方在
路径保存的时候每个节点要返回前面路径节点的和
即:a[i,j] = a[i-1,j] + a[i,j-1]
这样就可以在中间段时得到结果,不必重复走走过的路段。
AC代码:
#include <iostream>
using namespace std;
struct Point
{
int x;
int y;
};
Point start,end,house,N,P;
int housedir[][2] = {{1,2},{2,1},{-1,2},{2,-1},{-2,1},{1,-2},{-1,-2},{-2,-1}};
int dir[][2]={{1,0},{0,1}};
char g[17][17];
int e[17][17];
int counter;
int dfs(int x,int y);
int main()
{
int i,j,t,tempx,tempy,flag;
start.x = start.y = 0;
scanf("%d",&t);
while (t--)
{
scanf("%d %d %d %d",&end.x,&end.y,&house.x,&house.y);
//-----------开始画图:
counter = 0;
for (i=0;i<=end.x+1;i++)
{
for (j=0;j<=end.y+1;j++)
{
e[i][j] = 0;
g[i][j] = '0';
}
}
g[house.x][house.y] = '1';
for (i=0;i<8;i++)
{
tempx = house.x+housedir[i][0];
tempy = house.y+housedir[i][1];
if (tempx>=0 && tempx<=end.x && tempy>=0 && tempy<=end.y)
{
g[tempx][tempy] = '1'; //1为不可走
}
}
if (g[end.x][end.y] == '1')
{
printf("0/n");
continue;
}
g[end.x][end.y] = '2';
counter = 0;
dfs(0,0);
printf("%d/n",counter);
}
return 0;
}
int dfs(int x,int y)
{
int i,tx,ty;
int b[2];
if (g[x][y] == '2')
{
counter++;
e[x][y] = 1;
return e[x][y];
}
for (i=0;i<2;i++)
{
tx = x + dir[i][0];
ty = y + dir[i][1];
if (tx>=0 && tx<=end.x && ty>=0 && ty<=end.y)
{
if (g[tx][ty] != '1')
{
if (e[tx][ty])
{
counter+=e[tx][ty];
//return e[tx][ty];
}
else
{
e[tx][ty] = dfs(tx,ty);
//return e[tx][ty];
}
}
}
// b[i] = e[tx][ty];
}
if (e[x+1][y] || e[x][y+1])
{
return e[x+1][y]+e[x][y+1];
}
return 0;
}
递推算法(最高效):(AC)
由以上的递归算法的实现上我更清楚地看到的题目的本质
从中可以看到其递推的公式,这样可以将算法效率提高至线性!
/ | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|
|
|
|
|
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 |
1 | 1 | 2 | h | 1 | h | 1 | 2 |
| 1 | 2 | 3 | 4 |
2 | 1 | h | 0 | 1 | 1 | h | 2 |
| 1 | 3 | 6 | 10 |
3 | 1 | 1 | 1 | h | 1 | 1 | 3 |
| 1 | 4 | 10 | 20 |
4 | 1 | h | 1 | 1 | 2 | h | 3 |
|
| | |
| |
5 | 1 | 1 | h | 1 | h | 0 | 3 |
| 或可得到某种数列?! | |||
6 | 1 | 2 | 2 | 3 | 3 | 3 | 6 |
| ||||
递推公式: | 类杨辉三角 | |||||||||||
a[i][j]= a[i-1][j]+a[i][j-1]; |
|
|
|
|
a[i,j]表示达到i,j的方案数。
递推if点(i,j)可达则a[i,j]=a[i-1,j]+a[i,j-1]
但要注意边界!
AC代码:
#include <iostream>
using namespace std;
struct Point
{
int x;
int y;
};
Point start,end,house;
int a[17][17];
int housedir[][2] = {{1,2},{2,1},{-1,2},{2,-1},{-2,1},{1,-2},{-1,-2},{-2,-1}};
int main()
{
int i,j,k,t,n,m,tempx,tempy,countx,county;
scanf("%d",&t);
while(t--)
{
memset(a,0,sizeof(a));
scanf("%d %d %d %d",&end.x,&end.y,&house.x,&house.y);
a[house.x][house.y] = -1;
for (i=0;i<8;i++)
{
tempx = house.x+housedir[i][0];
tempy = house.y+housedir[i][1];
if (tempx>=0 && tempx<=end.x && tempy>=0 && tempy<=end.y)
{
a[tempx][tempy] = -1; //不可达,方案数标记为-1
}
}
//注意边界!!
a[0][0] = 1;
for (i=0;i<=end.x;i++)
{
for (j=0;j<=end.y;j++)
{
if (i == 0 && j == 0) //首元素处理
continue;
if (a[i][j] == -1) //不可达标记处理
continue;
if (i-1<0 || a[i-1][j] == -1) //X边界处理和不可达标记处理
countx = 0;
else
countx = a[i-1][j];
if (j-1<0 || a[i][j-1] == -1) //Y边界处理和不可达标记处理
county = 0;
else
county = a[i][j-1];
a[i][j] = countx + county;
}
}
printf("%d/n",a[end.x][end.y]);
}
return 0;
}