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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章