【递归与递推】洛谷[NOIP2002 普及组] 过河卒

前言

          本题来自洛谷P1002.

          题目链接:[NOIP2002 普及组] 过河卒 - 洛谷

题目描述

棋盘上 AA 点有一个过河卒,需要走到目标 BB 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 CC 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,AA 点 (0, 0)(0,0)、BB 点 (n, m)(n,m),同样马的位置坐标是需要给出的。

c589724303963f29a2e0e10af943bebc.png

现在要求你计算出卒从 AA 点能够到达 BB 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个正整数,分别表示 BB 点坐标和马的坐标。

输出格式

一个整数,表示所有的路径条数。

输入输出样例

输入

6 6 3 3

输出 

6

说明/提示

对于 100% 的数据,1≤n,m≤20,0 ≤ 马的坐标 ≤20。

【题目来源】

NOIP 2002 普及组第四题


解题思路 

       乍看一眼这道题好像没有什么思路,那我们就试着找一找前面的方法数是多少,看看有没有什么规律,或者是否能够写出一个递推式,我在下图中把一些距离A点较近的方法数标了出来,看看有什么规律?

b7177c19d4944ad396a4c756b6d48f56.png

递推式的求取:我首先把A点所在行的前四列和A点所在列的前四行的方法数标记了出来,这个很好理解吧?因为过河卒只能向下或向右走所以一直向下或者一直向右的方法数只能有一种。那么接下来我们再看看内部的点的方法数。

e523ed0f6ad54263ae363f23446bfaf9.png

 什么?你对这些方法数的算法有点懵?没事,且听我慢慢道来。我先说结论吧,其实要算到达某一点的方法数,其实就是组合问题,大家应该都学过排列组合吧?注意这里是组合而不是排列。因为这个组合数是对方向的组合,就是说我在我现在的位置可以怎么走呢?是不是只有向右或向下两种方法数,而对于某个任意点,这里为了方便表述,就拿当前图中马的所在点C举例吧(但马的位置不一定在这里哦,这个是需要用户输入的)。要到达C点无论走多少步,是不是至少得有两个向下走的和四个向右走的?过河卒肯定不会多走,或者说过河卒肯定是走到达某一点的最短路径的。所以说我们可以从这些信息中得知,要到达C点,总步数为6步,而其中有2步必须向下,剩下的4步必须向右,相信,这一定难不倒大家了吧?就是在6步之中选择两步,这两步都是向下所以没有差异,是组合问题,所以可以知道从A点到达C点的方法数为a8aecad29fef40989f207ace893b4c8d.png

也就是15种,其他点的方法数可以像C点这样求出来,但是要是都这样算是不是太麻烦了?而且我们还没有加入马的控制点,能不能从已有的这些数据中找出一些规律呢?细心的朋友可能已经发现了就是对于内部的点到达任意一个点的方法数等于到达它上面点的方法数再加上到达它左面点方法数(而对于边界上的点(比如A点所在行上的点的方法数只取决于它左边点的方法数,而对于A点所在列上的点的方法数只取决于它上面点的方法数,这个我们编码的时候特殊处理一下就好)),很容易,我们能写出递推式来:f[i][j]=f[i-1][j]+f[i][j-1].

好,到此我们的问题已经解决了一半了,剩下的就是要来处理马的控制点。

马的控制点的处理:如果过河卒走到了马的控制点以后,他将不能再继续往下走,无论上下左右,所以我们必须要避开马的控制点。怎么避开呢?就是要找出马的控制点然后标记出来,将走到它的方法数置为0,说明我们走不到这点。

具体标记方法:对于所有马的控制点最多就是八个(再加马的位置),如图中所示,但是我们对于不同的马的位置要看看马的控制点有几个在我们的范围内,对于马的控制点我们先按照最多的来找,然后找出来再来判断它是否在范围内,找法:我们可以定义一个方向数组,因为对于给定的马的位置,其控制点的位置是确定的,而这个方向数组就是存放马的位置的偏移量,只有马的位置给出,再加上它的偏移量就可以得到对应的控制点,最后来判断控制点是否在范围内,如果在范围内就标记出来,否则无需进行其他操作。

来看看具体代码

#include <iostream>
using namespace std;
long long f[21][21];//方法数数组,防止超出数据范围定义为长整型 
bool is[21][21];//判断是否为马的控制点的数组,若为控制点更新为true 
//定义方向数组分别代表P1~P8 
int dx[]={2,1,-1,-2,-2,-1,1,2};//横坐标的偏移量 
int dy[]={1,2,2,1,-1,-2,-2,-1};//纵坐标的偏移量 
int main()
{  int n,m,mx,my;
   cin>>n>>m>>mx>>my;
   is[mx][my]=true;//注意不要漏掉马的位置,马所在的位置也算马的控制点 
   //标记马的控制点
   for(int i=0;i<8;i++){
   	//判断马的控制点是否在范围内,若在范围内,标记 
   	if(mx+dx[i]>=0&&mx+dx[i]<=n&&my+dy[i]>=0&&my+dy[i]<=m){
   		is[mx+dx[i]][my+dy[i]]=true;
	   }	   
   }
   f[0][0]=1;//A点到达A点的方法数为1,就是“不走” 
   //对于第一行和第一列,我们要将其设为边界,方便下面的递推
   for(int j=1;j<=m;j++){
   	   f[0][j]=f[0][j-1];
	//判断是否为马的控制点,若是,将方法数置0
	if(is[0][j]){
		f[0][j]=0;
	} 
   }
   //第一列同上面一样的做法
   for(int i=1;i<=n;i++){
   	  f[i][0]=f[i-1][0];
   	if(is[i][0]){
		f[i][0]=0;
	}   
   }
   //对于内部的点利用递推式 f[i][j]=f[i-1][j]+f[i][j-1]
   for(int i=1;i<=n;i++){
   	for(int j=1;j<=m;j++){
   		f[i][j]=f[i-1][j]+f[i][j-1];
   		if(is[i][j]){
   			f[i][j]=0;
		   }
	   }
   } 
   cout<<f[n][m];
   return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马看到什么是人决定的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值