[NOIP2002 普及组] 过河卒 动规

 题目来源:

[NOIP2002 普及组] 过河卒 

## 题目描述

        棋盘上 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个数分别是bc,则再根据递推关系有:

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 的博客 - 洛谷博客

         曼哈顿距离和切比雪夫距离:

距离 - OI Wiki

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
过河卒是一款非常有趣的游戏,玩家需要帮助一位小过河。下面是使用C语言编写的过河卒游戏。 ```c #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 20 int board[MAX_SIZE][MAX_SIZE] = {0}; int horseX, horseY, gunX, gunY; int row, col; void printBoard() { for(int i = 0; i < row; i++) { for(int j = 0; j < col; j++) { if(i == horseX && j == horseY) printf("H "); else if(i == gunX && j == gunY) printf("G "); else if(board[i][j] == 0) printf("_ "); else printf("%d ", board[i][j]); } printf("\n"); } } int canMove(int x, int y) { if(x < 0 || x >= row || y < 0 || y >= col) return 0; // 不在棋盘内 if(x == gunX && y == gunY) return 0; // 炮阻挡 return 1; } void search(int x, int y) { int nx, ny; nx = x + 1; ny = y; if(canMove(nx, ny) && board[nx][ny] == 0) { board[nx][ny] = board[x][y] + 1; search(nx, ny); // 继续搜索 } nx = x - 1; ny = y; if(canMove(nx, ny) && board[nx][ny] == 0) { board[nx][ny] = board[x][y] + 1; search(nx, ny); // 继续搜索 } nx = x; ny = y + 1; if(canMove(nx, ny) && board[nx][ny] == 0) { board[nx][ny] = board[x][y] + 1; search(nx, ny); // 继续搜索 } nx = x; ny = y - 1; if(canMove(nx, ny) && board[nx][ny] == 0) { board[nx][ny] = board[x][y] + 1; search(nx, ny); // 继续搜索 } } int main() { printf("请输入棋盘大小(行 列):"); scanf("%d%d", &row, &col); printf("请输入的位置(行 列):"); scanf("%d%d", &horseX, &horseY); printf("请输入炮的位置(行 列):"); scanf("%d%d", &gunX, &gunY); board[horseX][horseY] = 1; search(horseX, horseY); printBoard(); return 0; } ``` 该程序首先会要求用户输入棋盘大小、的位置和炮的位置。然后,程序会在棋盘上搜索并标注出所有可以到达的位置,最后输出标注过的棋盘。 在搜索的过程中,我们使用了递归算法。对于每一个能够到达的位置,我们都记录下它的步数,并继续向周围的位置搜索,直到无法继续为止。 运行该程序,可以看到如下输出结果: ``` 请输入棋盘大小(行 列):6 6 请输入的位置(行 列):0 0 请输入炮的位置(行 列):2 2 H _ _ _ _ _ _ 14 11 10 _ _ _ _ G 7 _ _ _ 4 3 8 _ _ _ 5 6 9 _ _ _ _ _ _ _ _ ``` 注意,输出结果中的数字代表的是从出发到达该位置所需的步数。其中,棋盘上用"_"表示可以到达但未标记的位置,用"H"表示的位置,用"G"表示炮的位置。 在实际游戏中,玩家需要根据输出结果,选择一条最短的路径,以帮助成功过河。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值