记忆化搜索

目录

一、前言

二、简要谈谈记忆化搜索

三、最长滑雪道

1、题目

2、基本思路

3、python代码

四、立方体IV

1、上链接

2、基本思路

3、C++代码

4、python代码

5、发现的C++与python之间的输入区别

五、食物链

1、上链接

2、基本思路

3、C++代码(7/10)

4、python代码(9/10)


一、前言

对于学计算机的同学来说,学习算法是一件非常重要的事情,废话不多讲,我们来讲讲“记忆化搜索问题”。

二、简要谈谈记忆化搜索

什么是记忆化搜索呢?搜索的低效在于没有能够很好地处理重叠子问题;动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。

用一个公式简单地说:记忆化搜索=搜索的形式+动态规划的思想

记忆化搜索是类似于动态规划的,不同的是,它是倒做的“递归式动态规划”。如斐波那契数列、滑雪问题等等。

三、最长滑雪道

1、题目

2、基本思路

解决一道题前,首先考虑用什么算法。

对于这道题而言,最直接朴素的想法应该是对于每个点进行深搜或者广搜,最后枚举一遍答案但是注意,对于每个点而言,只要周围的点比他小,都可以被走到,因此这样做的话时间复杂度是指数级别的,AC不掉。

那么我们考虑优化。

首先,搜索的思路是没有错的,那么怎样优化它呢?我们观察到一个条件,那就是,某个点只能从数值更大的点转移过来,也就是说,整个答案序列应该是递减的 => 搜索时不可能出现循环 => 搜索顺序是具有拓扑结构的,即,在一个固定答案序列中,数值较小的点一定是在数值较大的点后被搜索到,那么我们就可以使用记忆化搜索了。(注意上述所说:具有拓扑序是使用记忆化搜索的必备条件)

我们用一个数组储存从每个点出发可以达到的最大长度,之后,若是从其他数值较大的点转移到这个点时,不必再往后搜索了,因为结果已经记录在数组中了,直接返回就好

3、python代码

n,m=map(int,input().split())
Map=[]
ans=0
for i in range(n):
    Map.append(list(map(int,input().split())))

dp=[[-1 for i in range(m)] for j in range(n)] # 先列再行

def dfs(x,y):
    global dp
    if dp[x][y]!=-1:
        return dp[x][y]
    
    t=1
    for i,j in [(1,0),(0,1),(-1,0),(0,-1)]:
        px=x+i
        py=y+j
        if 0<=px<n and 0<=py<m and Map[px][py]<Map[x][y]:
            t=max(t,dfs(px,py)+1)
    
    dp[x][y]=t
    return t

for i in range(n):
    for j in range(m):
        dfs(i,j)
    
for i in range(n):
    for j in range(m):
        ans=max(ans,dp[i][j])

print(ans)

四、立方体IV

1、上链接

691. 立方体IV - AcWing题库

2、基本思路

分析过程完全相同,只是转移条件发生了小小的变化,下一个点必须比上一个点大1。注意一下输出格式即可。

3、C++代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=1010;

int n;
int g[N][N],f[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};

int dp(int x,int y){
	int& v=f[x][y];
	if(v!=-1) //f[x][y]不是-1说明它不是起点,其他点到当前点f[x][y],说明其他点的路径更长 
		return v;
	
	v=1;//四面都走不了说明我留在原地也是1 
	for(int i=0;i<4;i++){
		int a=x+dx[i],b=y+dy[i];
		if(a>=0&&a<n&&b>=0&&b<n&&g[a][b]==g[x][y]+1)
			v=max(v,dp(a,b)+1);
	}
	return v;
}

int main(){
	int T;
	cin>>T;
	for(int cases=1;cases<=T;++cases){
		cin>>n;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				cin>>g[i][j];
			}
		}
		memset(f,-1,sizeof f);
		
		int id,cnt=0;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				int t=dp(i,j);
				if(t>cnt || t==cnt && id>g[i][j]){ // &&比||的优先级高
					id=g[i][j];
					cnt=t;
				}
			}
		}
		printf("Case #%d: %d %d\n",cases,id,cnt);
	}
}

4、python代码

import copy

n=0
N=1100
g=[]
f=[[-1]*N for _ in range(N)]
_f=[[-1]*N for _ in range(N)]

def dp(x:int,y:int)->int:
    v=f[x][y]
    if v!=-1:
        return v;

    v=1
    for i,j in {(-1,0),(0,1),(1,0),(0,-1)}:
        a,b=x+i,y+j
        if 0<=a<n and 0<=b<n and g[a][b]==g[x][y]+1:
            v=max(v,dp(a,b)+1)

    f[x][y]=v
    return v


T=int(input())
for case in range(1,T+1):
    g.clear()    # 这里有个坑,要对列表进行清空
    n=int(input())  # 一旦回车便执行完毕
    for i in range(n):
        line=list(map(int,input().split()))
        g.append(line)

    f=copy.deepcopy(_f)

    _id,maxn=0,0

    for i in range(n):
        for j in range(n):
            t=dp(i,j)
            if t>maxn or t==maxn and _id>g[i][j]:
                _id=g[i][j]
                maxn=t

    print("Case #{}: {} {}".format(case,_id,maxn))
    

python代码是过不了的,原因在于输入的问题,下面有谈到这个问题。

5、发现的C++与python之间的输入区别

代码:

 终端输入输出:

我们能很清楚看到,cin一定要读取到内容,这样回车才能结束输入;

但是python的输入一旦回车马上结束输入,它会认为你输入了一个空字符。

五、食物链

1、上链接

2716. 食物链 - AcWing题库

2、基本思路

首先我们浏览题目 能得到一个很重要的线索:

这是一张拓扑图 即点与点之间存在拓扑关系,所以我们很自然的能想到使用记忆化搜素。

大体使用什么算法已经定下来了,接下来应该扣细节了。

我们可以想一下,怎样才能算作一条食物链,是不是一条从起点能走向终点的路径?

所以我们可以维护一个dp数组,数组储存从这个点出发(不一定是起点),有几条路径能走到终点。

那么起点和终点怎么判断呢,这里就需要用到拓扑排序中的术语了:起点(入度为0)、

终点(出度为0)

确定起点和终点之后,就需要思考这个搜索函数dfs该怎么写了。

首先我们可以先思考终点前的一个点,这个点的 dp 数组值是不是就应该是它连接着几个终点?比如A、B都是终点,然后 点C->A、C->B 有这两条路径,那么dp[C]=2 。

这样以此类推, 能通向 C 的点 D 就加上 dp[C] 的值和从点 D 能到达的点的 dp 值,如此往复递归直到起点。

需要注意的是,如果一个点已经被遍历过了,那么它的 dp 值就不会再被改变了,因为它的 dp 值只能被该点的下一个点更改。由于拓扑序的关系,该点之后的点不可能再被遍历到,因此该点的 dp 值不会再被改变。

3、C++代码(7/10)

#include<iostream>
#include<queue>
#include<vector>
using namespace std;

const int N=100009;
int n,m;
int in[N],out[N],chain[N];
vector<vector<int>> f(N,vector<int>(0,0));

int toposort(int x){
	int s=0;
	queue<int> q;
	q.push(x);
	while(!q.empty()){
		int now=q.front();
		q.pop();
		int len=f[now].size();
//		if(len==0&&in[now]!=0){
//			
//		}
		for(int i=0;i<len;i++){
			if(out[f[now][i]]==0)
				s+=1;
			else
				q.push(f[now][i]);
		}
	}
	return s;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		int a,b;
		cin>>a>>b;
		out[a]++;
		in[b]++;
		f[a].push_back(b);
	}
	
	int sum=0;
	for(int i=1;i<=n;++i){
		if(in[i]==0){
			sum+=toposort(i);
		}
	}
	cout<<sum<<endl;
	return 0;
} 

4、python代码(9/10)

import sys
sys.setrecursionlimit(1000000) # 修改递归深度 其他语言可以忽略
 
n,m = map(int,input().split())
 
Map = [dict() for i in range(n+1)]   # 用字典来减少空间!!!
din = [0 for i in range(n+1)] # 每个点的入度
dout = [0 for i in range(n+1)] # 每个点的出度
 
for i in range(m) :
    a,b = map(int,input().split())
    din[b] += 1 ; dout[a] += 1
    Map[a][b] = 1
    
ST = [] ; EN = [] # 起点 终点
    
for i in range(1,n+1) :
    if din[i] == 0 : ST.append(i)
    elif dout[i] == 0 : EN.append(i)
    
dp = [-1 for i in range(n+1)] # 初始化为-1
 
def dfs(node) :
    global dp
    if dp[node] != -1 : # 如果被遍历过直接返回
        return dp[node]
    if node in EN : return 1 # 如果是终点返回 1
    dp[node] = 0
    for next in Map[node].keys() : # 否则累加该点能到的所有点的dp值
        dp[node] += dfs(next)
        
    return dp[node] # 返回
 
ans = 0    
for i in ST : dfs(i) ; ans += dp[i] # 从每个起点开始走 累加答案
print(ans)
    

注意领会其中思想即可,至于没完全AC我也是调了很久没办法(先挖个坑,迟点有空修正)。

以上,记忆化搜索

祝好

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕飞雨的头发不能秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值