8.21 DP专题:记忆化搜索+例题P1048 NOIP2005 普及组 采药

记忆化搜索

  • 一、概念
  • 二、例子--P1048 NOIP2005 普及组 采药
    • 朴素的DFS做法
    • 优化:记忆化搜索
    • 递推(与记忆化搜索形式上高度相似)
  • 三、写记忆化搜索的步骤


一、概念

记忆化搜索是通过记录已经遍历过的状态的信息,确保了每个状态只访问一次,避免对同一状态重复遍历的搜索实现方式

二、例子–P1048 NOIP2005 普及组 采药

P1048 NOIP2005 普及组 采药

朴素的DFS做法

/*朴素的DFS做法:搜索时记录下当前枚举到第几个物品、所剩时间、已经获得的价值 
同一个状态会被访问多次,时间复杂度是指数级别的,评测结果TLE*/
#include
using namespace std;
int T,m,ans;
struct node{
	int t,w;
}a[105];
void dfs(int p,int t1,int w1){
	if(t1<0) return;
	if(p==m+1){
		ans=max(ans,w1);
		return ;	
	}
	dfs(p+1,t1,w1);
	dfs(p+1,t1-a[p].t,w1+a[p].w);
}
int main(){
	cin>>T>>m;
	for(int i=1;i<=m;i++) cin>>a[i].t>>a[i].w;
	dfs(1,T,0);
	cout<<ans;
}

优化:记忆化搜索

用空间换取时间

/*记忆化搜索:数组mem记录每个dfs(p,t1)的返回值
用空间换取时间,时间复杂度为O( TM )*/
#include
using namespace std;
int T,m;
struct node{
	int t,w;
}a[105];
int mem[105][1005]; 
int dfs(int p,int t1){
	if(mem[p][t1]!=(-1))return mem[p][t1];
	if(p==m+1){
		return mem[p][t1]=0;	
	}
	int d1=-0x3f3f3f3f,d2;  //注意初始化
	if(t1>=a[p].t){
		d1=dfs(p+1,t1-a[p].t)+a[p].w;
	}
	d2=dfs(p+1,t1);
	return mem[p][t1]=max(d1,d2);
}
int main(){
	memset(mem,-1,sizeof(mem));//注意初始化
	cin>>T>>m;
	for(int i=1;i<=m;i++) cin>>a[i].t>>a[i].w;
	cout<<dfs(1,T);
}

递推(与记忆化搜索形式上高度相似)

两者使用相同的状态表示方式和类似的状态转移方程,递推的时间复杂度也为O( TM )

/*递推*/
#include
using namespace std;
int T,m;
struct node{
	int t,w;
}a[105];
int f[105][1005];//f[i][j] 时间j内 枚举到第i个药品获得的最大的价值 
int main(){
	cin>>T>>m;
	for(int i=1;i<=m;i++) cin>>a[i].t>>a[i].w;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=T;j++){
			f[i][j]=f[i-1][j];
			if(j>=a[i].t){
				f[i][j]=max(f[i][j],f[i-1][j-a[i].t]+a[i].w);
			}
		}
	}
	cout<<f[m][T];
}

不同的是,递推通过设置明确的访问顺序来避免重复访问,记忆化搜索虽然没有明确规定访问顺序,但通过给已经访问过的状态打标记的方式,也达到了同样的目的。
记忆化搜索相较于递推比较方便地处理边界情况。但,记忆化搜索有时运行效率会低于递推。因此应该视题目选择更适合的实现方式。

三、写记忆化搜索的步骤

方法一
1.把这道题的 dp 状态和方程写出来
2.根据它们写出 dfs 函数
3.添加记忆化数组

方法二
·1.写出这道题的暴搜程序(最好是 dfs)
2.将这个 dfs 改成“无需外部变量”的 dfs
3.添加记忆化数组

你可能感兴趣的:(算法打卡学习,深度优先,算法,动态规划)