过河卒
原题链接:传送
题目描述
棋盘上 AA 点有一个过河卒,需要走到目标 BB 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 CC 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,AA 点 (0, 0)(0,0)、BB 点 (n, m)(n,m),同样马的位置坐标是需要给出的
现在要求你计算出卒从 AA 点能够到达 BB 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入格式
一行四个正整数,分别表示 B 点坐标和马的坐标。
输出格式
一个整数,表示所有的路径条数。
输入输出样例
输入 #1
6 6 3 3
输出 #1
6
说明/提示
对于 %100 的数据,1≤n,m≤20,0≤ 马的坐标 ≤20。
问题分析:
第一个错误是错在题意的理解上面,虽然这道题的背景是中国象棋,但是在看到卒过河后只能向下和向右时就有点怀疑,到底是为了防止陷入左右左右左的死循环,还是为了提醒我们这不是中国象棋的规则呢。
结果就是我想多了,对后面马的行走方式“跳跃一步”我以为就是上下左右的一步而已,而不是日字走法,其实还忽略了图的重要性,图中已经标明了马的控制点位置。
第二个困难就是这是我第一次用C++写题,还有一些语法的问题。
这是我第一次写的代码
#include <iostream>
#include <vector>
using namespace std;
int main(){
int x1,x2,y1,y2;
int i,j;
cin >> x1;
cin >> y1;
cin >> x2;
cin >> y2;
vector< vector<long long int> > a(x1+1);
for ( i =0; i<(x1+1); i++ )
{
a[i].resize(y1+1);
}
for ( i=0; i<(x1+1); i++ )
{
a[i][0] = 1;
}
for ( j=0; j<(y1+1); j++ )
{
a[0][j] = 1;
}
for ( i=1; i<(x1+1); i++ )
for ( j=1; j<(y1+1); j++ )
{
a[i][j] = a[i-1][j] + a[i][j-1];
for ( i=x2-1; i<=(x2+1); i++ )
for ( j=y2-1; j<=(y2+1); j++ )
a[i][j] = 0;
}
cout << a[x1][y1];
return 0;
}
其实这个很容易就可以得出状态转移方程,即
a[i][j] = a[i-1][j] + a[i][j-1];
然后因为之前有数组越界的经验,所以就将第一行和第一列都初始化为1,从1,1开始算起(其实这里就错了,因为可能第一行和第一列中就有马的控制点,又因为卒不能上走,故若在第一行控制点的右边全为0),马的行走方式也误认为是上下左右一格,每写完一个的数值后动态刷新马控制点为0.
虽然整个思路也好,代码也好都是错的,但运行不了!!哪怕是简单的数据也严重超时,看了好几遍也不知道到底问题出现在哪里,估计是vector没用好,但水平有限真的检查不出来(哭)。
然后就还是换用普通的数组了(以最大数值情况创建数组),也改正了马的行走方式,依旧采用每次写入都动态刷新控制点的做法,代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
int fx[]={0,-2,-2,-1,-1,1,1,2,2};
int fy[]={0,1,-1,2,-2,2,-2,1,-1};
long long int a[21][21];
int i,j;
void reset(int x2, int y2);
int main(){
int x1,x2,y1,y2;
cin>>x1>>y1>>x2>>y2;
for ( i=0; i<(x1+1); i++ )
a[i][0]=1;
for ( i=0; i<(y1+1); i++ )
a[0][i]=1;
for ( i=1; i<(x1+1); i++ )
for ( j=1; j<(y1+1); j++ )
{
a[i][j] = a[i-1][j] + a[i][j-1];
reset(x2,y2);
}
cout<<a[x1][y1]<<endl;
return 0;
}
void reset(int x2, int y2){
for ( i=0; i<9; i++ )
{
a[x2+fx[i]][y2+fx[i]] = 0;
}
}
虽然这次解决了马行走方式的问题,但还是没有解决一个问题,就是因为可能第一行和第一列中就有马的控制点,又因为卒不能上走,故若在第一行控制点的右边全为0。
那么现在我的问题就是如何找到新的算法解决第0行和列越界的情况,最终还是求助于洛谷的题解,看了别人的解法后都然大悟!!
既然我要解决越界问题,其实直接判断这到底是不是第0行或列,然后揪出来特殊处理不就好了。题解的方法十分巧妙,将状态转移方程直接拆成两个部分,分别对应行和列。
题解中还解决了一个我很担心的问题,缩短了时间复杂度,就是每次的reset,在我上面的解法中,我每次通过状态转移方程写入数据时都要把马的9个控制点重新归零,要1个循环,九个步骤,特别担心超时。但题解中直接利用bool数组记录,只用循环一次即可,同时为了防止越界,也限制了条件在前。
AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
//马的9个控制点
int fx[]={0,-2,-2,-1,-1,1,1,2,2};
int fy[]={0,1,-1,2,-2,2,-2,1,-1};
//只有BP[0][0]被赋值为1,其余均为0,这是全局变量的特性
long long int BP[21][21]={1};
//同样利用的是全局变量的特性
//但因为这个其实和最后的运算相反,需要再用到!
//当然,也可以这里做处理,for循环赋初始值为1
bool MARK[21][21];
int main(){
int x1,x2,y1,y2;
int i,j;
cin>>x1>>y1>>x2>>y2;
//通过MARK数组记录马的9个控制点
for ( i=0; i<9; i++ )
{
//if判断是为了防止数组发生越界,只能在表盘范围内的点,越界就不管啦
if ( x2+fx[i]>=0 && x2+fx[i]<=x1 && y2+fy[i]>=0 && y2+fy[i]<=y1 )
MARK[x2+fx[i]][y2+fy[i]] = 1;
}
for ( i=0; i<=x1; i++ )
for ( j=0; j<=y1; j++ )
{
if(i)
BP[i][j] += BP[i-1][j];
if(j)
BP[i][j] += BP[i][j-1];
//特别重要的一个地方!
//当该点被刚刚标记为控制点后,就要归零,这里用的是乘法
BP[i][j] *= !MARK[i][j];
}
cout<<BP[x1][y1];
return 0;
}