dp解决路径数问题

例一、P1002 过河卒

题意:给出卒的坐标和敌方马的坐标,求卒不经过马的攻击范围从(0,0)点到达(n,m)的路径数
思路:设dp[ i ] [ j ] 为从(0,0)到达 ( i , j ) 点的路径数。可以得到转移方程:
d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+d[i][j-1] dp[i][j]=dp[i1][j]+d[i][j1]
递推时,跳过马的攻击点即可

#include <iostream>
#define ll long long
using namespace std;

int n,m,x,y;
ll dp[21][21];
int visit[21][21];
int moves[][2]={0,0,-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2,-2,-1};

int main()
{
	cin>>n>>m>>x>>y;
	dp[0][0]=1;
	
	for(int i=0;i<=8;++i)
	{
		int nx=x+moves[i][0];
		int ny=y+moves[i][1];
		if(nx>=0&&nx<=n&&ny>=0&&ny<=m)
			visit[nx][ny]=1;
	}	
	for(int i=0;i<=n;++i)
	{
		for(int j=0;j<=m;++j)
		{
			if(!visit[i][j])
			{
				if(i)
					dp[i][j]+=dp[i-1][j];
				if(j)
					dp[i][j]+=dp[i][j-1];
			}
		}
	}
	cout<<dp[n][m]<<endl;  
	return 0;
}

2、把第一维压缩成 2
注意:正确的代码是:dp[i&1][j]=dp[(i-1)&1][j]; 而不是:dp[i&1][j]+=dp[(i-1)&1][j];
原因:该位置的值代表的是(i-2,j)位置的路径数,再加上dp[ i-1 ][ j ] 的值,没有任何意义。

#include <iostream>
#define ll long long
using namespace std;

int n,m,x,y;
ll dp[2][21];
int visit[21][21];
int moves[][2]={0,0,-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2,-2,-1};

int main()
{
	cin>>n>>m>>x>>y;
	dp[0][0]=1;
	
	for(int i=0;i<=8;++i)
	{
		int nx=x+moves[i][0];
		int ny=y+moves[i][1];
		if(nx>=0&&nx<=n&&ny>=0&&ny<=m)
			visit[nx][ny]=1;
	}	
	for(int i=0;i<=n;++i)
	{
		for(int j=0;j<=m;++j)
		{
			if(!visit[i][j])
			{
				if(i)
					dp[i&1][j]=dp[(i-1)&1][j];
				if(j)
					dp[i&1][j]+=dp[i&1][j-1];
			}
			else
				dp[i&1][j]=0; 
		}
	}
	cout<<dp[n&1][m]<<endl;  
	return 0;
}

3、直接压缩成一维

#include <iostream>
#define ll long long
using namespace std;

int n,m,x,y;
ll dp[21];
int visit[21][21];
int moves[][2]={0,0,-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2,-2,-1};

int main()
{
	cin>>n>>m>>x>>y;
	dp[0]=1;
	
	for(int i=0;i<=8;++i)
	{
		int nx=x+moves[i][0];
		int ny=y+moves[i][1];
		if(nx>=0&&nx<=n&&ny>=0&&ny<=m)
			visit[nx][ny]=1;
	}	
	for(int i=0;i<=n;++i)
	{
		for(int j=0;j<=m;++j)
		{
			if(!visit[i][j])
			{
				if(j)
					dp[j]+=dp[j-1];			
			}
			else
				dp[j]=0; 
		}
	}
	cout<<dp[m]<<endl;  
	return 0;
}

例二、P1006 传纸条

题意:从(1,1)到(n,m)传两张纸条,要求只能往下或者往右,两条路径不重复,并且使得同学的好心程度最大。求同学的好心程度
思路:从(1,1)到(n,m)最多要走n+m-2步,我们可以枚举步数。让两张纸条同时传送。设dp[step][i][j]表示走了step - 2步后,靠左的纸条在第i列,靠右的纸条在第j列。同时它们的横坐标是可以直接算出来的
转移方程 d p [ s ] [ i ] [ j ] = m a x ( d p [ s − 1 ] [ i − 1 ] [ j − 1 ] , d p [ s − 1 ] [ i − 1 ] [ j ] , d p [ s − 1 ] [ i ] [ j − 1 ] , d p [ s − 1 ] [ i ] [ j ] ) + a [ s − i ] [ i ] + a [ s − j ] [ j ] dp[s][i][j]=max(dp[s-1][i-1][j-1],dp[s-1][i-1][j],dp[s-1][i][j-1],dp[s-1][i][j])+a[s-i][i]+a[s-j][j] dp[s][i][j]=max(dp[s1][i1][j1],dp[s1][i1][j],dp[s1][i][j1],dp[s1][i][j])+a[si][i]+a[sj][j]

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m;
int dp[105][55][55],a[55][55];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; ++i)
        for(int j=1; j<=m; ++j)
            scanf("%d",&a[i][j]);
    memset(dp,-0x3f,sizeof(dp));
    dp[3][2][1]=a[2][1]+a[1][2];
    for(int i=4; i<=n+m; ++i)
    {
        for(int j=1; j<=n; ++j)
        {
            for(int k=1; k<j; ++k)
            {
                if(k+1==j)
                {
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][j-1]+a[j][i-j]+a[k][i-k]);
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][j-2]+a[j][i-j]+a[k][i-k]);
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][j-2]+a[j][i-j]+a[k][i-k]);
                }
                else
                {
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k]+a[j][i-j]+a[k][i-k]);
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+a[j][i-j]+a[k][i-k]);
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+a[j][i-j]+a[k][i-k]);
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k-1]+a[j][i-j]+a[k][i-k]);
                }
            }
        }
    }
    printf("%d\n",dp[n+m-1][n][n-1]);
    return 0;
}
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <cstring>
#include <string>
#include <cmath>
#define rep(i,a,b) for (int i=a; i<=b; ++i)
#define per(i,b,a) for (int i=b; i>=a; --i)
#define mes(a,b)  memset(a,b,sizeof(a))
#define mp make_pair
#define ll long long
#define pb push_back
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define isZero(d)  (abs(d) < 1e-8)
using namespace std;
const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

int n,m,a[55][55];
int dp[110][55][55];


int main()
{
	cin>>n>>m;
	rep(i,1,n)
		rep(j,1,m)
			cin>>a[i][j];
	mes(dp,-1);	
	dp[2][1][1]=0;
	for(int step=3;step<=n+m-1;++step)
	{
		for(int i=1;i<m;++i)
		{
			for(int j=i+1;j<=m;++j)
			{
				dp[step][i][j]=max(dp[step-1][i-1][j-1],dp[step][i][j]);
				dp[step][i][j]=max(dp[step-1][i-1][j],dp[step][i][j]);
				dp[step][i][j]=max(dp[step-1][i][j-1],dp[step][i][j]);
				dp[step][i][j]=max(dp[step-1][i][j],dp[step][i][j]);
				if(dp[step][i][j]==-1)
					continue;
				dp[step][i][j]+=a[step-i][i]+a[step-j][j];
			}			
		}
	}
	cout<<dp[n+m-1][m-1][m]<<endl;
	return 0;
}

例三、P1057 传球游戏

题意:n个人围成一圈传球,从小蛮开始传球,经过m次又传给小蛮的方法数
思路:设dp[i][j]表示经过 j 次传球又回到第 i 个人手中的方案数。从0号开始传球,又回到0号手中,答案就是dp[0][m]
转移方程
d p [ i ] [ j ] = d p [ ( i − 1 + n ) % n ] [ j − 1 ] + d p [ ( i + 1 ) % n ] [ j − 1 ] dp[i][j]=dp[(i-1+n){\%n}][j-1]+dp[(i+1)\%n][j-1] dp[i][j]=dp[(i1+n)%n][j1]+dp[(i+1)%n][j1]
注意:循环的顺序是不可以调换的。我们先从小到大枚举次数,然后再枚举最终球落在某个人的位置的编号。如果我们先枚举了人的编号,那么在更新dp[i][j]的时候,我们所需要的dp[i+1][j-1]其实是没有做过更新的。其实观察转移方程就能知道,循环的顺序了

#include <iostream>
using namespace std;
int n,m;
int dp[35][35];
int main()
{
	cin>>n>>m;
	dp[0][0]=1;
	for(int j=1;j<=m;++j)
		for(int i=0;i<=n-1;++i)
			dp[i][j]=dp[(i-1+n)%n][j-1]+dp[(i+1)%n][j-1];
    cout<<dp[0][m]<<endl;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值