[NOIP2002 普及组] 过河卒【暴搜+DP详解+解析时间复杂度】

😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊

[NOIP2002 普及组] 过河卒

题目描述

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

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

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

输入格式

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

输出格式

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

样例 #1

样例输入 #1

6 6 3 3

样例输出 #1

6

提示

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

【题目来源】

NOIP 2002 普及组第四题

小白到进阶各种解法:

一、暴搜:详细分析在DP阶段,可以先看DP,易懂😊

在这里插入图片描述

代码:

#include<iostream>

using namespace std;
const int N = 23;
int st[N][N];
int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
int b1, b2, m1, m2;
int ans;

void dfs(int x, int y)
{
	if (x == b1 && y == b2)
	{
		ans ++;
		return ;
	}
	
	//两个方向上去搜索!
	if (x+1<=b1 && st[x+1][y]==0)	//没有越界,并且下一个点不是障碍物!	往下走!
		dfs (x+1, y);
		
	if (y+1<=b2 && st[x][y+1]==0)	//往右走 
		dfs (x, y+1);
}

int main()
{
	cin >> b1 >> b2 >> m1 >> m2;    //输入的是B点坐标及马的坐标!
	st[m1][m2] = true;  //标记为true!
    
    //标记由该马所引发的所有障碍物!
    //共8个方向!
    for (int i=0; i < 8; i ++)
    {
        int a = m1 + dx[i], b = m2 + dy[i];
        if (a >= 0 && a <= b1 && b >= 0 && b <= b2)
            st[a][b] = 1;
    }
    
    dfs (0, 0);
    cout << ans << endl;
    
	return 0;
}

在这里插入图片描述

二、记忆化搜索:待更新😊

在这里插入图片描述

代码:AC不了!有错!

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 23;
int st[N][N];
int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
int b1, b2, m1, m2;
long long f[N][N];f[x][y]表示从(x,y)走到终点的方案数!	
开longlong:走到(n,m)点一共要走(n+m)步,所以说先从n+m里面选出n步,剩余的步数就是向下的了,方案数为:C(n+m,n); 

long long dfs(int x, int y)
{
	if (f[x][y] != -1) 	若下次走到同样的点时,就直接返回之前的方案数,再走一遍也是一样的!
		return f[x][y];
	
	if (x == b1 && y == b2)	//走到终点,给xy赋值为1,表示从当前点走到终点的方案为1;
		return 1;
	
	//两个方向上去搜索!
	if (x+1<=b1 && st[x+1][y]==0)	//没有越界,并且下一个点不是障碍物!	往下走!
		f[x][y] += dfs (x+1, y);
		
	if (y+1<=b2 && st[x][y+1]==0)	//往右走 
		f[x][y] += dfs (x, y+1);
	return f[x][y];
}

int main()
{
	cin >> b1 >> b2 >> m1 >> m2;    //输入的是B点坐标及马的坐标!
	st[m1][m2] = true;  //标记为true!
    
    //标记由该马所引发的所有障碍物!
    //共8个方向!
    for (int i=0; i < 8; i ++)
    {
        int a = m1 + dx[i], b = m2 + dy[i];
        if (a >= 0 && a <= b1 && b >= 0 && b <= b2)
            st[a][b] = 1;
    }
    
    cout << dfs (0, 0);
    
	return 0;
}

在这里插入图片描述

三、本题考察算法:动态规划😊

思路:


时间复杂度的分析:

首先解释为何本题的方案数会爆 i n t int int
在这里插入图片描述

在这个题目中,我们需要求出卒从 (0,0) 点走到 (n, m)点的方案数。因为卒每次只能向
右或者向下走,所以它一共需要向右走 n 步,向下走 m 步。而我们只需要确定在这(n
+m)中,选择哪 n 步向右走,其余的就必须向下走。

所以,总方案数就是在 $n+m$ 步中选择 n 步向右走的方案数,即:C(n+m,n)步;组合数公式如下图;

当 n=20, m=20的时候,直接计算结果为:137846528820,超过了 int 的范围,会导致计算结果出错。

所以说我们需要用 long long 来存储这个结果。

在这里插入图片描述
若本题用暴搜的话会超时:
因为搜索的时间复杂度是指数级别的,随着棋盘大小的增加,搜索的状态数会呈指数级别增长,导致搜索时间过长。因此需要采用其他算法,比如动态规划。

搜索算法的时间复杂度取决于搜索空间的大小和搜索算法的策略。在搜索的过程中,每个节点可能有多个子节点需要遍历,因此搜索的时间复杂度会随着搜索空间的增加而增加。

对于一般的搜索问题,搜索空间往往非常大,尤其是在状态数量呈指数增长的情况下。例如,在本题中,每个位置上可能有卒或马或者空位三种状态,棋盘大小为 n × m n \times m n×m,搜索空间的大小为 3 n × m 3^{n \times m} 3n×m,在 n , m n,m n,m 比较大的情况下,这个搜索空间的大小会非常庞大,因此暴力搜索的时间复杂度就会呈指数级别增长,很容易超时。

因此,在这种情况下,我们需要设计更加高效的搜索策略和剪枝技巧,来尽可能减少搜索空间的大小,减少搜索的时间复杂度。

假设棋盘大小为 n × m n\times m n×m,卒初始位置为 A ( x 0 , y 0 ) A(x_0,y_0) A(x0,y0),目标位置为 B ( x B , y B ) B(x_B,y_B) B(xB,yB),马的位置为 H ( x H , y H ) H(x_H,y_H) H(xH,yH)

我们可以将搜索算法看作一个树形结构,其中每个节点表示卒在棋盘上的一个位置,每个节点的子节点表示卒从该位置走一步所能到达的位置。在这个树形结构中,每个节点的出度不超过 2 2 2(因为卒只能向下或向右走),因此树形结构的最大深度不会超过 n + m n+m n+m

对于每个节点,我们需要判断它是否合法(即是否在棋盘内且不被马所控制),因此每个节点的时间复杂度为 O ( 1 ) O(1) O(1)。因为树形结构的最大深度为 n + m n+m n+m,因此整个搜索的时间复杂度为 O ( ( n + m ) 2 ) O((n+m)^2) O((n+m)2)

需要注意的是,虽然搜索算法的时间复杂度是多项式级别的,但由于本题的棋盘较小,搜索树的分支因子较小,因此搜索算法仍然可以在规定时间内解决本题。


分析完时间复杂度后,我们开始来实现本题:

  1. 本题为二维平面上求方案数模型,对于终点 ( n , m ) (n,m) nm,我们不妨思考其是如何转移而来的,终点 (n, m) 的最后一步转移由两个选择,分别是由: ( n − 1 , m ) , ( n , m − 1 ) (n-1, m), (n,m-1) (n1,m),(n,m1) 所转移来的。而我们要求的是方案数:所以到达 ( n , m ) (n,m) (n,m) 的方案数 = 到达 ( n , m − 1 ) (n,m-1) (n,m1) 的方案数 + 到达 ( n − 1 , m ) (n-1,m) (n1,m)的方案数之和。而 ( n − 1 , m ) 和 ( n , m − 1 ) (n-1, m)和(n,m-1) (n1,m)(n,m1)的方案数又是如何得到的呢?
  2. 所以说我们继续向上思考, ( n − 1 , m ) , ( n , m − 1 ) (n-1, m), (n,m-1) (n1,m),(n,m1) 又是如何转移得到的了?这不就是可以构造出子问题推导大问题了吗?并且子问题的求解不受后面的问题求解的影响,也符合打表推导的性质,所以 ⇒ DP。
  3. 既然是二维平面则状态方程也可以设为二维的:
    f [ i ] [ j ] : f[i][j]: f[i][j]表示从起点到 ( i , j ) (i,j) (i,j) 的方案数。
  4. 状态计算: f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] ; f[i][j] = f[i-1][j] + f[i][j-1]; f[i][j]=f[i1][j]+f[i][j1]

代码:

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 100;
int st[N][N];   //标记障碍物!
long long f[N][N];
int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};

int main()
{
    int b1, b2, m1, m2;
    cin >> b1 >> b2 >> m1 >> m2;    //输入的是B点坐标及马的坐标!
    
    //马的坐标也是障碍物!
    st[m1][m2] = true;  //标记为true!
    
    //标记由该马所引发的所有障碍物!
    //共8个方向!
    for (int i=0; i < 8; i ++)
    {
        int a = m1 + dx[i], b = m2 + dy[i];
        if (a >= 0 && a <= b1 && b >= 0 && b <= b2)
            st[a][b] = 1;
    }
    
    //初始化边界:
    //有8列!
    for (int i=0; i <= b2; i ++)
    {
        if (st[0][i]) break;
		f[0][i] = true; 
    }
    
    //有4行:
    for (int i=0; i <= b1; i ++)
    {
        if (st[i][0]) break;
        f[i][0] = true;
    }
    
    //开始递推: 
    for (int i=1; i <= b1; i ++)
    {
    	for (int j=1; j <= b2; j ++)
    	{
    		if (st[i][j]) continue;
    		f[i][j] = f[i-1][j] + f[i][j-1];
		}
	}

    cout << f[b1][b2] << endl;
    return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值