dfs+dp的两道例题(地宫取宝,格子刷油漆)

本文介绍了如何使用dfs+dp解决地宫取宝和格子刷油漆的问题。地宫取宝中,通过dfs优化避免超时,结合dp存储状态;格子刷油漆问题则直接采用dp求解。关键在于定义状态和状态转移方程,通过递推公式减少计算量。总结指出,当dfs超时时,可利用dp加快计算,同时理解dp与状态之间的关系至关重要。
摘要由CSDN通过智能技术生成

概念

dfs

  dfs的目的是要达到被搜索结构的目标地址,下面是dfs的代码逻辑。

void dfs(int x,int y,int cnt)   //x,y为地址坐标,cnt为深度
{
    if(x>n-1 || y>m-1 || cnt>k)  //剪枝
        return;
    if(x==n-1 && y==m-1 ){   //到达目标
        ans=ans+1;
        return;
    }
    for(int i=0;i<4;i++)  //遍历情况
    {
        int tx=x+dir[0][i];
        int ty=y+dir[1][i];
        if(!book[tx][ty]) {   //book标记 
        	book[tx][ty]=1;
            dfs(tx,ty,cnt+1);
            book[tx][ty]=0;
        }
    }
    return;
}

dp

  • 状态 :例如背包问题中,前i 个物品在空间为j的状态下的最大价值。
  • 状态转移方程:边界的定义,状态和状态之间的关系
  • 储存当前最优状态的值
#include<string.h>
int dp[100][600];
int N[100];
int dfs(int num,int sum){
    if(dp[num][sum]!=-1)return dp[num][sum];

    if(sum==500)
        return dp[num][sum]=1;
    if(num>=100||sum>500)
        return dp[num][sum]=0;

    int ans=0;
    ans+=dfs(num+1,sum);//选
ans+=dfs(num+1,sum+N[num]);//不选
return dp[num][sum]=ans;
}
int main()
{
    memset(dp,-1,sizeof(dp));
}

地宫取宝

资源限制

时间限制:1.0s 内存限制:256.0MB

问题描述

X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
输入格式
  输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)

接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值

输出格式

  要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。

样例输入

2 2 2
1 2
2 1
样例输出
2

样例输入

2 3 2
1 2 3
2 1 5
样例输出
14

思路

  dfs对于较大数组遍历的时候,因为多次递归会超时,这里提出一个解决的办法,将每次遍历的值储存在dp中,这里有几个注意的点:

  • 要判断该状态之前是否遍历,这个对dp初始化为-1
  • dfs 的出口,满足条件返回1,不满足条件返回0,dfs首先返回的一定是叶子节点,是一个置顶向上的过程
  • 对于中间过程,一个状态对应多个状态,该状态的值是后面状态的和值
  • 最后,每一次返回都要进行记录一次当前状态的值,便于下次直接调用

代码

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
using namespace std;
typedef long long ll;

int n,m,k;

int dir[2][2]={{1,0},{0,1}};
int dp[55][55][15][15];
int N[55][55];

ll dfs(int x,int y,int num,int value)
{
    if(dp[x][y][num][value+1]!=-1)
        return dp[x][y][num][value+1];
    if(x==n && y==m){
        if(num==k || (num==k-1 && N[x][y]>value))
            return dp[x][y][num][value+1]=1;
        else return dp[x][y][num][value+1]=0;
    }
    ll cnt=0;
    for(int i=0;i<2;i++)
    {
        int tx=x+dir[0][i];
        int ty=y+dir[1][i];

        if(tx>n || ty >m)
            continue;
        if(value < N[x][y])
            cnt+=dfs(tx,ty,num+1,N[x][y]);
        cnt+=dfs(tx,ty,num,value);
    }

    return dp[x][y][num][value+1]=cnt%1000000007;
}

int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>N[i][j];

    memset(dp,-1,sizeof(dp));
    dfs(1,1,0,-1);
    cout<<dp[1][1][0][0];
}

格子刷油漆

问题描述

  X国的一段古城墙的顶端可以看成 2*N个格子组成的矩形(如下图所示),现需要把这些格子刷上保护漆。

  你可以从任意一个格子刷起,刷完一格,可以移动到和它相邻的格子(对角相邻也算数),但不能移动到较远的格子(因为油漆未干不能踩!)
  比如:a d b c e f 就是合格的刷漆顺序。
  c e f d a b 是另一种合适的方案。
  当已知 N 时,求总的方案数。当N较大时,结果会迅速增大,请把结果对 1000000007 (十亿零七) 取模。
输入格式
  输入数据为一个正整数(不大于1000)
输出格式
  输出数据为一个正整数。

样例输入

2
样例输出
24

样例输入

3
样例输出
96

样例输入

22
样例输出
359635897

思路

  随着n的增大,递归的深度越深,运算量会急剧增加,不适合用深搜,联想递推即动态规划来解决这个问题。
  dp[0][i]表示不返回,只去。dp[1][i]表示返回到出发列的另一行。表示列数为i时,一共有多少种方法。

  • 从顶点出发,下一步走向该列的下一行,dp[0][i]=2*dp[0][i-1]
  • 从顶点出发,下一步走向下一列,之后返回上一列,dp[0][i]=2*2*dp[0][i-2]
  • 从顶点出发,下一步走向下一列,之后继续下一列,dp[1][i]=2*dp[1][i-1]

dp[0][i]=2*dp[0][i-1]+4*dp[0][i-2]+2*dp[1][i-1]
dp[1][i]=2*dp[1][i-1]
sum=4*dp[0][i]

  • 从中间一个点出发,注意这个地方dp[0][i]包含dp[1][i]的方案
  • sum=(2*dp[0][i-1]*dp[1][n-i+1]+dp[1][i]*2*dp[0][n-i])*2

  当然,还需要注意初始值的情况,当n=1的时候,只有两种走法,当n=2的时候,从一个顶点出发有6种情况。

代码

#include<iostream>
#include<string>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll dp[2][1005];
ll ans;
int n;
int main()
{
	cin>>n;
	if(n==1)
	{
		cout<<2<<endl;
		return 0;
	}
	dp[0][1]=1;
	dp[0][2]=6;
	dp[1][1]=1;
	dp[1][2]=2;
	for(int i=3;i<=n;i++)
	{
		dp[1][i]=(2*dp[1][i-1])%mod;
		dp[0][i]=(2*dp[0][i-1]+dp[1][i]+4*dp[0][i-2])%mod;
	}
	ans=4*dp[0][n]%mod;
	for(int i=2;i<n;i++)
		ans=(ans+4*(dp[0][i-1]*dp[1][n-i+1]+dp[1][i]*dp[0][n-i]))%mod;
	cout<<ans<<endl;
	return 0;
}

解决办法

  dfs超时的时候,记录下当前状态的值,但一定要优先记录最下层的递归值。dp就需要找到递推公式,并且分情当前状态。

总结

  • 一般dp的题目用dfs可以解答,但在有时间条件的限制下,dfs超时了就要用到dp,一半dp用for循环完成,但在一些dfs的题目中,用dfs遍历状态,dp来加快计算,减少遍历次数。
  • 动态规划的重点在于,定义节点的状态,每个节点的最优状态可以从之前某个或某几个节点通过运算得到,了解这个关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值