关于记忆化搜索

一.定义:

指在搜索的过程中将计算结果记录下来,使之后不再重复计算同一个状态。

动态规划可以用记忆化搜索实现,但记忆化搜索不一定能用动态规划。

二.优缺点:

优点
1.边界条件很好处理
2.可以不用关心处理的顺序

缺点
1.不能用滚动数组进行优化
2.递归层数过多容易爆栈

三.注意事项:

1.\(dp\)值不能设为中间状态可能出现的结果
2.状态转移右边需要写\(dfs\)而不是\(dp\)值。

例如:在dp[x]=dfs(x-1)+dfs(x-2)中,右边不能替换成dp[x-1]+dp[x-2]

3.边界条件要先进行判断

典型例题

例一 一本通1201 裴波那契数列

很经典的一个题目,思路简单

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=25;

int dp[MAXN];

inline int dfs(int x)
{
	if(x==1)
		return 1;
	if(x==2)
		return 1;
	if(dp[x]!=0)
		return dp[x];
	dp[x]=dfs(x-1)+dfs(x-2);
	return dp[x];
}

int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int a;
		scanf("%d",&a);
		printf("%d\n",dfs(a));
	}
	return 0;
}

例二 P1002 过河卒

这也是一道很经典的题目,只是要注意要开\(long\ long\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=25;

int dp[MAXN][MAXN];
int n,m,x,y;

int cx[]={-1,-2,1,2,-2,-1,1,2,0};
int cy[]={-2,-1,-2,-1,1,2,2,1,0};

inline int dfs(int a,int b)
{
	if(a>=0&&b>=0)
	{
		if(a==0&&b==0)
			return 1;
		if(dp[a][b]!=-1)
			return dp[a][b];
		dp[a][b]=dfs(a,b-1)+dfs(a-1,b);
		return dp[a][b];
	}
	return 0;
}

signed main()
{
	memset(dp,-1,sizeof(dp));
	scanf("%lld%lld%lld%lld",&n,&m,&x,&y);
	for(register int i=0;i<=8;i++)
	{
		if(x+cx[i]>=0 && x+cx[i]<=n && y+cy[i]>=0 && y+cy[i]<=m)
		{
			dp[x+cx[i]][y+cy[i]]=0;
		}
	}
	printf("%lld",dfs(n,m));
	return 0;
}

例三 一本通P1281 最长上升子序列

同样是思路很简单的一道题

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e3+5;

int n,a[MAXN];
int dp[MAXN];

inline int dfs(int x)
{
	if(dp[x]!=-1)
		return dp[x];
	dp[x]=1; 
	for(register int i=1;i<=x-1;i++)
	{
		if(a[i]<a[x])
		{
			dp[x]=max(dp[x],dfs(i)+1);
		}
	}
	return dp[x];
}

int main()
{
	scanf("%d",&n);
	for(register int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	memset(dp,-1,sizeof(dp));
	int maxn=-1;
	for(register int i=1;i<=n;i++)
	{
		maxn=max(maxn,dfs(i));
	}
	printf("%d",maxn);
	return 0;
}

例四 P1434 滑雪

这道题有一丢丢特殊,注释在代码里

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;

int a[MAXN][MAXN],dp[MAXN][MAXN];
int n,m;

int dx[]={0,0,1,-1};//上下左右挪动 
int dy[]={1,-1,0,0};

inline int dfs(int x,int y)
{
	if(dp[x][y]!=-1)//若已求过,直接输出 
		return dp[x][y];
	dp[x][y]=1;
	for(register int i=0;i<=3;i++)//循环上下左右 
	{
		int nx=x+dx[i];//上下左右的坐标 
		int ny=y+dy[i];
		if(nx>=1&&ny>=1&&nx<=n&&ny<=m&&a[x][y]>a[nx][ny])//不能越界 
			dp[x][y]=max(dp[x][y],dfs(nx,ny)+1);//状态转移 
	}
	return dp[x][y];
}

int main()
{
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	memset(dp,-1,sizeof(dp));//初始化为-1 
	int ans=-1;
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=m;j++)
			ans=max(ans,dfs(i,j));//取所有的最大值 
	printf("%d\n",ans);
	return 0;
}

例五 P7074 方格取数

这题很不一样,由于有只有上、右、下三个方向,所以不用考虑从左边的。

这里定义\(dp_{i,j,k}\)表示走到坐标为\((i,j)\)的点是的最大值,若\(k\)\(0\),则是从上面走下来的,反之是从下面走上去的。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e3+5;
const int INF=1e17;

int a[MAXN][MAXN],dp[MAXN][MAXN][2];
int n,m;

inline int dfs(int x,int y,int k)
{
	if(x==1&&y==1&&k==0)
		return a[1][1];
	if(x<1||y<1||x>n||y>m)	
		return -INF;
	if(dp[x][y][k]!=-INF)
		return dp[x][y][k];
	if(k==0)
	{
		dp[x][y][k]=max(dfs(x,y-1,0)+a[x][y],dfs(x,y-1,1)+a[x][y]);
		dp[x][y][k]=max(dp[x][y][0],dfs(x-1,y,0)+a[x][y]);
	}
	else
	{
		dp[x][y][k]=max(dfs(x,y-1,0)+a[x][y],dfs(x,y-1,1)+a[x][y]);
		dp[x][y][k]=max(dp[x][y][1],dfs(x+1,y,1)+a[x][y]);
	}
	return dp[x][y][k];
}

signed main()
{
	scanf("%lld%lld",&n,&m);
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<=m;j++)
		{
			scanf("%lld",&a[i][j]);
			dp[i][j][0]=dp[i][j][1]=-INF;
		}
	printf("%lld",dfs(n,m,0));
	return 0;
}
posted @ 2022-05-25 21:46  Code_AC  阅读(117)  评论(0)    收藏  举报