题目来源:
## 题目描述
棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A 点 (0, 0)、B 点 (n, m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
## 输入格式
一行四个正整数,分别表示 B 点坐标和马的坐标。
## 输出格式
一个整数,表示所有的路径条数。
数组优化:
将dp由二维压成两个一维数组:
由于递推公式里只需要两个数值,一个是本行左侧的值,另一个是本行右侧的值,所以每次只记录这两个值,然后不断覆盖就好了:这很像斐波那契数列的数组压缩,如果第i个数是a[i](i>0),有递推公式a[i]=a[i-1]+a[i-2](i>2),本递推公式后两项也可以只用两个数(将两个一维数组压成两个整形),比如a,b(分别表示第i=k个数(k>2)的前一个第i-2和第i-1个数)而当前第i个数的值可以用c记录,那么有
c=a+b
那么到第i+1个数时,为了节省空间而要覆盖a(第i+1-3个数)的值,第i+1-2个数和第i+1-1个数分别是b和c,则再根据递推关系有:
a=b+c
我们可以得到一个表格用来表示当前和前几个数的关系:
i | i+1 | i+2 | i+3 | i+4 | i+5 | |
i | c | a | b | c | a | b |
i-1 | b | c | a | b | c | a |
i-2 | a | b | c | a | b | c |
i-3 | ? | a | b | c | a | b |
比如a[1],a[2]两个整形(这里只开两个,便于后面取余所以用数组表示:int a[3]),第i个数可以用a[0]来表示,按上面的过程不断推导,递推结果可以写作:
我们不难看出,这里的空间压缩并不理想化:每次都导致了冗余一个数来存储下一次将不会被用到(本身毫无用处)的数:即最后一行。我们观察发现i+1递推时第i-1个数只会用到一次,而一旦用递推公式求出i+1后存储就毫无意义了(毕竟i+2不会用到i-1来求)所以不如直接求出i+1后赋在i-1上,也就出现了下面的形式:
a,b(分别表示第i=k个数(k>2)的前一个第i-2和第i-1个数)那么第i个数可这样求:
a=a+b
进而第1+1个数有:
b=b+a
后面的递推两式将会循环出现。。。
用两个一维数组a[0],a[1]来表示就是
数学推导一下不难有:
这是由两个一维递推得第三个数,那本题的两个二维得第三个数又该怎样呢?
实际上本题并不没有更加复杂,反而更简单了:虽然是二维,但转化为两个一维数组存储后递推形式不变,仍为
我们求第i行的数,是为了用于求第i+1行的数,而第i-1行的数与i+1行的递推无关,因此在求第i行的数时可以在保留第i行数的基础上覆盖第i-1行的数,同理第i+1行的数也会覆盖第i行的数,那么我们不妨让这个数也被覆盖,那么就有了下面的递推关系式:
进一步写出代码:
#include<bits/stdc++.h>
using namespace std;
int a[21][21],n,m,ex,ey;
long long step[2][22];
int D[][2]={{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int main()
{
step[0][1]=1;
cin>>ex>>ey>>m>>n;
ex++;
ey++;
m++;
n++;
for(int k=0;k<8;k++)
{
if(m+D[k][0]<=0||m+D[k][0]>ex+1||n+D[k][1]<=0||n+D[k][1]>ey+1)continue;
else
a[m+D[k][0]][n+D[k][1]]=-1;
}
a[m][n]=-1;
for(int i=1;i<=ex;i++)
for(int j=1;j<=ey;j++)
{
if(a[i][j]==-1){
step [(i)%2][j]=0;continue;
}
step[(i)%2][j]=step[(i-1)%2][j]+step[(i)%2][j-1];
}
cout<<step[(ex)%2][ey]<<endl;
return 0;
}
当然数组还可以被压缩,,,
和上面的递推过程大致一样,我们不难发现dp[(i-1)%1]只会在求dp[i%2]时用到,申请的空间将毫无意义,(由于本身递推数组的错位效应)我们还可以将其压缩成为:
dp[j]=dp[j-1]+dp[j]
由于dp(压缩前)数组本身是个二2*maxj维数组压缩后则变成了1*maxj的一维数组,而这里的dp[j-1]就是dp[i][j-1],而dp[j]是未更新的dp[i-1][j]。
于是我们可以再次写出:
#include<bits/stdc++.h>
using namespace std;
int a[21][21],n,m,ex,ey;
long long step[2][22];
int D[][2]={{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int main()
{
step[0][1]=1;
cin>>ex>>ey>>m>>n;
ex++;
ey++;
m++;
n++;
for(int k=0;k<8;k++)
{
if(m+D[k][0]<=0||m+D[k][0]>ex+1||n+D[k][1]<=0||n+D[k][1]>ey+1)continue;
else
a[m+D[k][0]][n+D[k][1]]=-1;
}
a[m][n]=-1;
for(int i=1;i<=ex;i++)
for(int j=1;j<=ey;j++)
{
if(a[i][j]==-1){
step [(i)%2][j]=0;continue;
}
step[(i)%2][j]=step[(i-1)%2][j]+step[(i)%2][j-1];
}
cout<<step[(ex)%2][ey]<<endl;
return 0;
}
当然,由于马行走的特殊性,可以用切比雪夫距离和曼哈顿距离来限定,从而消去a[i][j]。
感谢Chiaro的题解给予我的启发和思考,如需借鉴请看:
题解 P1002 【过河卒】 - Chiaro 的博客 - 洛谷博客
曼哈顿距离和切比雪夫距离: