NOIP 2002 过河卒(记忆化DFS||DP)

 

题目描述

如图,A点有一个过河卒,需要走到目标B点。卒行走规则:可以向下、或者向右。同时在棋盘上的任一点有一个对方的马(如图中的C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点。例如图中C 点上的马可以控制9个点(图中的P1,P2...P8 和C)。卒不能通过对方马的控制点。 

棋盘用坐标表示,A点(0,0)、B点(n,m)(n,m 为不超过20的整数,并由键盘输入),同样马的位置坐标是需要给出的(约定:C<>A,同时C<>B)。现在要求你计算出卒从A点能够到达B点的路径的条数。 

 

输入

每个测试文件只包含一组测试数据,每组输入四个整数n,m,x,y。((n,m)表示B点的坐标,(x,y)表示对方马的坐标) 

 

输出

对于每组输入数据,输出一个整数,表示路径的条数。 

 

分析:到终点的路径是由之前的点一个一个递推来的,并且每到一个点,是不用管之前的状态是怎么来的,只需要用现在的状态又去不断递推后面的状态,那么其实也就满足了最优子结构,并且无后效性,那么也就想到了用DP来做,dp(i,j)表示从起点到(i,j)这个点的走法有多少种,然后把马本身和马的控制范围的点都打上标记,表明这些点不能走,将dp[0][0]赋初值为1,然后DP即可。。。其实这种问题很容易也可以想到用递归来做,但不能常规递归,因为中间重复计算了很多次,所以要用记忆化搜索。 注意数字较大,用long long 能过。

 

代码1  记忆化搜索:

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstring>
#include<utility>
#include<cstdlib>
#include<iomanip>
#include<iostream>
#include<algorithm>
#define Clear(x) memset(x,0,sizeof(x))
#define fup(i,a,b) for(int i=a;i<b;i++)
#define rfup(i,a,b) for(int i=a;i<=b;i++)
#define fdn(i,a,b) for(int i=a;i>b;i--)
#define rfdn(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
using namespace std;
const int maxn = 27;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1.0);
const double eps = 1e-3;
int vis[maxn][maxn];
ll f[maxn][maxn];
int n,m,x,y;

int read()
{
    char ch=getchar();int ret=0,f=1;
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
    return f*ret;
}

ll DFS(int x,int y)
{
    if(f[x][y]) return f[x][y];
    if(x>n||y>m) return 0;
    if(x==n&&y==m) return 1;
    if(vis[x+1][y]==0&&x+1<=n) f[x][y]+=DFS(x+1,y);
    if(vis[x][y+1]==0&&y+1<=m) f[x][y]+=DFS(x,y+1);
    return f[x][y];
}

int main()
{
    n=read(),m=read(),x=read(),y=read();
    Clear(f);
    Clear(vis);
    vis[x][y]=1;
    vis[x-1][y-2]=1,vis[x-2][y-1]=1,vis[x-2][y+1]=1,vis[x-1][y+2]=1;
    vis[x+1][y+2]=1,vis[x+2][y+1]=1,vis[x+2][y-1]=1,vis[x+1][y-2]=1;
    cout<<DFS(0,0)<<endl;
    return 0;
}

 

代码2  dp:

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstring>
#include<utility>
#include<cstdlib>
#include<iomanip>
#include<iostream>
#include<algorithm>
#define Clear(x) memset(x,0,sizeof(x))
#define fup(i,a,b) for(int i=a;i<b;i++)
#define rfup(i,a,b) for(int i=a;i<=b;i++)
#define fdn(i,a,b) for(int i=a;i>b;i--)
#define rfdn(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
using namespace std;
const int maxn = 27;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1.0);
const double eps = 1e-3;
int vis[maxn][maxn];
ll dp[maxn][maxn];//表示从起点走到(i,j)处有几条路径
int dir[][2]={{0,0},{-1,-2},{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2}};
int n,m,x,y;

int read()
{
    char ch=getchar();int ret=0,f=1;
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
    return f*ret;
}

void mark()
{
    for(int i=0;i<9;i++)
    {
        if(x+dir[i][0]>=0&&x+dir[i][0]<=n&&y+dir[i][1]>=0&&y+dir[i][1]<=m)
            vis[x+dir[i][0]][y+dir[i][1]]=1;
    }
}

void slove()
{
    dp[0][0]=1;
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            if(vis[i][j]) continue;
            if(i) dp[i][j]+=dp[i-1][j];//如果不在第一行就要加上上面一行递推过来的路径数
            if(j) dp[i][j]+=dp[i][j-1];//如果不在第一列就要加上上面一行递推过来的路径数
        }
    }
    /**
    可以打出路径便于理解
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            printf("%d ",dp[i][j]);
        }
        printf("\n");
    }*/
    printf("%lld\n",dp[n][m]);
}

int main()
{
    n=read(),m=read(),x=read(),y=read();
    Clear(dp);
    Clear(vis);
    mark();
    slove();
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 这是一道描述棋盘上走棋规则的问题。给出了三个点aa、bb、cc,要求从aa点到bb点走一步,需要沿下方向或者向右方向走。同时,在棋盘上有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此,需要注意能够到达bb点的点必须不在对方马的控制点上。这个问题称为“马拦河卒”。 ### 回答2: 这道题类似于迷宫探索。我们可以把棋盘看成一个坐标系,卒的位置为起点,目标点为终点。既然卒只能向下或向右移动,那么我们只需要让卒向着目标点最短的方向移动,直到到达目标点即可。 但是,这道题的难点在于对方的马。因为马能够控制到的点都是对卒的威胁,所以我们需要先判断对方马能否对卒造成威胁。如果对方马距离卒的横向距离大于等于3,纵向距离大于等于3,则该马无法将卒拦截,卒可以直接行走。如果对方马距离卒的横向距离小于2,纵向距离小于2,则该马直接将卒拦截。如果对方马距离卒的横向距离等于2,纵向距离等于2,则需要根据对方马的具体位置进行判断。 如果对方马距离卒的横向距离等于2,纵向距离等于2,可以划分成四个方向判断。以对方马的位置为中心,向四个方向分别判断对应的点是否存在,如果存在则需要判断对方马是否能够到达该点,如果能够到达,则该点受到对方马的威胁。 如果卒能够避开对方马的威胁,就可以按照最短路径向目标点靠近。如果卒不能避开对方马的威胁,就需要考虑如何绕路。可以考虑分别向上和向左走一步,检查两个新点是否受到对方马的威胁,如果有威胁则需要再次绕路,直到找到安全的路径。 总之,在这种情况下,我们需要通过细致的分析来找出最优解决方案,保证卒能够安全到达目标点。 ### 回答3: 过河卒是中国象棋中的一种棋子,只能向前走,且要过河后才能横着走。现在有一个过河卒需要走到目标点,但是在棋盘上还有一个对方的马,要阻止过河卒的前进。 首先,我们需要了解棋盘的基本结构,其实就是一个8*8的方格棋盘。每个象棋棋子的行走路线都有它们自己的规则和限制。过河卒可以向下或向右走,但是不能向上或者向左走。所以我们需要根据这个规则来寻找到达目标点的最短路径。 而对方的马则是另一个挑战。马的行走路线是一个“日”字形。当它的位置在过河卒前进的路径上或者可以跳到过河卒的行进路线上时,过河卒就会被拦住。因此,我们需要绕过对方的马,选择路径避开对方马的控制点。 面对这个问题,我们可以通过回溯算法来解决。回溯算法是一种广泛使用的算法,可以用来寻找所有有效的路径。我们可以从起点开始,每次尝试向下或向右移动一步,同时检查是否到达了目标点,并考虑到对方马的控制点会对路径产生影响。如果被对方马拦住,我们需要绕路避开控制点,然后继续前进。最终,当找到一条到达目标点的有效路径时,就可以停止搜索。 总之,找到过河卒到目标点的最短路径并绕开对方马的控制点是一个有挑战性的问题,但通过回溯算法,我们可以找到所有的有效路径,并且选出最短路径来解决这个问题。再复杂的问题,只要用正确的方法,都可以被解决。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值