记忆化搜索笔记

集训时一不小心透露了自己不懂记忆化搜索,于是就。。

概述

记忆化搜索采用的是搜索,在求解时从顶向下求解,每求解一个状态,就将它的解保存下来,在之后遇到重复的状态时,可以不必重新求解。简单来说,就是我们记录一下遇到的每一个状态的值。

特点

全局最优,一定会用一个数组或其他存储结构存储之前得到的子问题的解(空间换时间)

适用范围

必须是分步计算,且搜索过程中的一个搜索的结果必须建立在同类型问题的基础上(分治)当一个题目根据具体判断可能会出现重复的答案时候,或是不加以优化的暴力搜索会超时的时候极其适合

思想

根据动态规划方程写出递归式,下函数的开头直接返回以前计算过的结果(需要一个存储结构来记录之前算过的结果)

下面是我被题目虐时间

1.洛谷经典function(P1464)

题目:

对于一个递归函数w(a,b,c)w(a,b,c)

如果a≤0 or b≤0 or c≤0就返回值11

如果a>20 or b>20 or c>20就返回w(20,20,20)

如果a<b并且b<c 就返回w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c)w(a,b,c−1)+w(a,b−1,c−1)−w(a,b−1,c)

其它的情况就返回w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1)w(a−1,b,c)+w(a−1,b−1,c)+w(a−1,b,c−1)−w(a−1,b−1,c−1)

输入格式

会有若干行,并以-1,-1,-1−1,−1,−1结束。

保证输入的数在[-9223372036854775808,9223372036854775807][−9223372036854775808,9223372036854775807]之间,并且是整数。

输出格式

输出若干行,每一行格式:w(a, b, c) = ans

注意空格。输入格式,会有若干行,并以-1,-1,-1−1,−1,−1结束。

热爱暴力的我先上了我的传统艺能:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll f[30][30][30];
int w(ll a, ll b, ll c){    
    if(a<=0||b<=0||c<=0){
        return 1;
    }
    else if(a>20||b>20||c>20){
        return w(20,20,20);
    }
    else if(a<b&&b<c){
        return w(a,b,c-1) + w(a,b-1,c-1) - w(a,b-1,c);
    }
    else{
        return w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
    }
}

int main(){
    ll a, b, c;
    while(1){
        cin >> a >> b >> c;
        if(a==-1&&b==-1&&c==-1) return 0;
        else
            printf("w(%lld, %lld, %lld) = %d\n", a, b, c, w(a, b, c));
    }
    return 0;
}

结果你懂得:
tle

我枯了....

绞尽脑汁想出来一个办法:

记忆化搜索!!

开辟一个数组 p[][][],用来存储计算出来的结果。

因为题目中给出了一个条件 “ 如果 a>20 or b>20 or c>20就返回w(20,20,20) ” 那么数组只要最小开到 f[21][21][21]就够用了。先用for跑一遍把数据先存储到数组内,以后调用就不用递归了!!

代码:

#include<bits/stdc++.h>
using namespace std;
int p[21][21][21];
int a,b,c;
int main()
{
    
    for (int i=0;i<=20;++i)
      for (int j=0;j<=20;++j)
        for (int k=0;k<=20;++k)
          p[i][j][k]=1;
    for (int i=1;i<=20;++i)
      for (int j=1;j<=20;++j)
        for (int k=1;k<=20;++k)
          p[i][j][k]=p[i-1][j][k]+p[i-1][j-1][k]+p[i-1][j][k-1]-p[i-1][j-1][k-1];
    for (;;)
    {
        scanf("%d%d%d",&a,&b,&c);
        if (a==-1&&b==-1&&c==-1) 
			return 0;
        if (a<=0||b<=0||c<=0)
        {
            printf("w(%d, %d, %d) = %d\n",a,b,c,1);
            continue;
        }
        if (a>20||b>20||c>20)
        {
            printf("w(%d, %d, %d) = %d\n",a,b,c,p[20][20][20]);
            continue;
        }
        printf("w(%d, %d, %d) = %d\n",a,b,c,p[a-1][b][c]+p[a-1][b-1][c]+p[a-1][b][c-1]-p[a-1][b-1][c-1]);
    }
    return 0;
}

2.POJ 1088 滑雪

Problem Description

Michael喜欢滑雪,这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。

下面是一个例子

1 2 3 4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

Input

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

Output

输出最长区域的长度。

Sample Input

5 5

1 2 3 4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

Sample Output

25

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int map[110][110],f[110][110],n,m;
int dx[5]={0,0,1,-1};
int dy[5]={1,-1,0,0};
int dfs(int x,int y) {
    if(f[x][y]!=-1) 
    	return f[x][y];
    f[x][y]=0;
    int sum=0;
    for(int i=0;i<5;i++) 
    {
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&map[x][y]>map[xx][yy]) 
            sum=max(sum,dfs(xx,yy));
    }
    return f[x][y]=sum+1;
}//通过存储每个数组的最优解实现记忆化
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>map[i][j];
    memset(f,-1,sizeof(f));
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) 
		{
            int x=dfs(i,j);
            ans=max(ans,x);
        }
    cout<<ans<<endl;
    return 0;
}

谢谢观看!!(求赞求评论)

posted @ 2020-08-02 14:00  horizon?  阅读(66)  评论(0)    收藏  举报