每日刷题——洛谷P1002过河卒(dfs和dp心得)

364 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

P1002 [NOIP2002 普及组] 过河卒

题目描述

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

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

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

输入格式

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

输出格式

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

样例 #1

样例输入 #1

6 6 3 3

样例输出 #1

6

提示

对于 100%100 \% 的数据,1n,m201 \le n, m \le 2000 \le 马的坐标 20\le 20

【题目来源】

NOIP 2002 普及组第四题

思路1:dfs

最先想到是dfs暴力搜索,毕竟昨天才刷的暴力枚举题单。

先设出方向数组,注意卒只能向下或向右

再设一个pos数组,用于标记坐标是否被马控制,这里注意马本身坐标也是控制点

其次再设置一个gud函数用来判断坐标点是否越界,方便复用

接下来dfs函数就很好写了,从原点出发,出界或是踩到控制点则return,否则就向右或向下继续深搜。这里千万注意向右、下踏出一步后要将x,y减回原位置再踏出下一步。

代码

// P1002 [NOIP2002 普及组] 过河卒
// dfs
#include <bits/stdc++.h>
using namespace std;

int n,m,mn,mm;
int pos[21][21] = {0};
int cnt = 0;
int dir[2][2]=
{
    {1,0},
    {0,1}
};

bool gud(int x, int y){
    //判断坐标为x,y的点是否在范围内
    if(x<=n && x>=0 && y <=m && y>=0)
    return true;
    else return false;
}

void dfs(int x,int y){
    if(x == n && y == m){   //到达B点
        cnt++;
        return;
    }
    if(!gud(x,y) || pos[x][y]) return;  //越界或踩到控制点
    
    for(int i=0; i<2; i++){ //朝下或朝右前进
        x += dir[i][0];
        y += dir[i][1];
        dfs(x,y);
        x -= dir[i][0];
        y -= dir[i][1];
    }
}

int main(){
    cin >> n >> m >> mn >> mm;
    //将马的控制点设为1
    pos[mn][mm] = 1;
    pos[mn-2][mm+1] = 1;
    pos[mn-2][mm-1] = 1;
    pos[mn-1][mm+2] = 1;
    pos[mn-1][mm-2] = 1;
    pos[mn+2][mm+1] = 1;
    pos[mn+2][mm-1] = 1;
    pos[mn+1][mm+2] = 1;
    pos[mn+1][mm-2] = 1;
    //开始深搜
    dfs(0,0);
    cout << cnt;
    return 0;
}

然后TLE了,只能再寻它法

思路2:dp递推

因为每一个坐标都是由上和左两个位置移动而来,因此如果设数组dp[i][j]表示到达i,j坐标下的路径数,那么可以列出如下公式 dp[i][j] = dp[i-1][j] + dp[i][j-1]

本来是写了一个dp的递归函数,然后和dfs一样TLE了。

最后看了别人的题解才知道,直接双重for循环,以行顺序遍历坐标求dp数组就可以了,直接的利用递推公式进行计算的复杂度要比调用递归函数的时间效率高很多。初始化原点的dp数组值为1,然后遇到控制点略过,这样所有的控制点的dp数组值为默认的0,并且注意跳过原点的dp数组计算,以免重复赋值

// P1002 [NOIP2002 普及组] 过河卒
// dp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n,m,mn,mm;
int pos[30][30] = {0};
ll dp[30][30] = {0};

bool gud(int x, int y){
    //判断坐标为x,y的点是否在范围内
    if(x<=n && x>=1 && y <=m && y>=1)
    return true;
    else return false;
}

int main(){
    cin >> n >> m >> mn >> mm;
    n++;
    m++;
    mn++;
    mm++;
    //将马的控制点设为1
    pos[mn][mm] = 1;
    pos[mn-2][mm+1] = 1;
    pos[mn-2][mm-1] = 1;
    pos[mn-1][mm+2] = 1;
    pos[mn-1][mm-2] = 1;
    pos[mn+2][mm+1] = 1;
    pos[mn+2][mm-1] = 1;
    pos[mn+1][mm+2] = 1;
    pos[mn+1][mm-2] = 1;
    //开始dp
    dp[1][1] = 1;
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){//从2开始是因为dp[1][1]已经被初始化,不能被改
        if(i==1 && j==1) continue;
            if(!pos[i][j]){
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
    }
    cout << dp[n][m];
    return 0;
}