关于记忆化搜索
一.定义:
指在搜索的过程中将计算结果记录下来,使之后不再重复计算同一个状态。
动态规划可以用记忆化搜索实现,但记忆化搜索不一定能用动态规划。
二.优缺点:
优点:
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;
}

浙公网安备 33010602011771号