POJ1088 滑雪 经典DP 记忆化搜索和递推

题目链接:http://poj.org/problem?id=1088

题目内容:

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

Source


遇到这样的题目,先大概估算一下时间,假如用暴力搜索,对每个点都dfs一次,显然在行数和列数最大为100的情况下,必然超时。那么不妨看看用动态规划,应该怎么解决:

假设dp(i,j)以点(i,j)为起点的最长路径的长度,那么dp(i,j)等于什么呢,根据题意,某个点可以转移到另一个点,当且仅当另一个点是其上下左右的相邻点,而且高度减小。

那么可以得出dp(i,j)==max(dp(i',j')+1),(i',j')是符合题意的(i,j)的相邻点。

而答案就是所有dp(i,j)中的最大值。

1、记忆化搜索

暴力搜索的缺点在于做了大量的重复的工作,比如在求dp(0,0)的时候,假设(0,0)可以转移到(0,1),那么在求出dp(0,0)的时候,dp(0,1)必然被求了出来,当下次某个点会转移到(0,1)时,又要重新求一次dp(0,1)。

动态规划的一个思想就是,用空间换时间,既然我在转移的过程中,把某个点(i,j)的dp(i,j)给求了出来,那么为了避免下次其他点转移到这个点又要重复工作,干脆就把已经求出来的dp(i,j)的值记录下来,那么下次再遇到的时候,直接查表。这样,可以保证每个点(i,j)都只求一次dp(i,j)。

附上AC代码

#include<iostream>
#include<algorithm>
#include<string.h>

#define width 105

using namespace std;

int maps[width][width];//存地图的高度
int dp[width][width];//记录dp(i,j)
int r,c;//行数和列数

int dfs(int x, int y)//深度优先搜索求解dp(x,y)
{
    if(dp[x][y]!=0)//当dp(x,y)!=0,表示该点(x,y)的dp(x,y)已经被求过了
    {
        return dp[x][y];//直接返回dp(x,y)
    }
    else
    {
        dp[x][y]=1;//某点(x,y)的dp(x,y)至少为1
        for(int i=-1;i<=1;i++)
        {
            for(int j=-1;j<=1;j++)
            {
                if(i==0&&j!=0||i!=0&&j==0)//遍历四个相邻点
                {
                    int newx=x+i;
                    int newy=y+j;
                    if(newx>=0&&newx<r&&newy>=0&&newy<c)//相邻点要在地图范围内
                    {
                        if(maps[newx][newy]<maps[x][y])//相邻点的高度要比该点低
                        {
                            dp[x][y]=max(dfs(newx,newy)+1,dp[x][y]);//深度优先搜索该相邻点的路径长度
                        }
                    }
                }
            }
        }
        return dp[x][y];
    }
}

int main()
{
    int ans=0;
    cin>>r>>c;
    for(int i=0;i<r;i++)
    {
        for(int j=0;j<c;j++)
        {
            cin>>maps[i][j];
        }
    }
    memset(dp,0,sizeof(dp));
    for(int i=0;i<r;i++)
    {
        for(int j=0;j<c;j++)
        {
            if(dp[i][j]==0)//当dp(i,j)==0,表示该点(i,j)的dp(i,j)没被计算过
            {
                dfs(i,j);//计算该点(i,j)的dp(i,j)
            }
            if(dp[i][j]>ans)
            {
                ans=dp[i][j];//记录已经求出的所有的dp(i,j)的最大值
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}


那么如果用递推而不是递归,又该怎么办呢? 看下面一个例子

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=17

单调递增最长子序列

描述

求一个字符串的最长递增子序列的长度
如:dabdbf最长递增子序列就是abdf,长度为4
输入
第一行一个整数0<n<20,表示有n个字符串要处理
随后的n行,每行有一个字符串,该字符串的长度不会超过10000
输出
输出字符串的最长递增子序列的长度
样例输入
3
aaa
ababc
abklmncdefg
样例输出
1
3
7


Longest Increasing Subsequence单调递增最长子序列,很经典的一道动态规划题目

如果用动态规划解决,那么应该很容易想到:

假设dp(i)表示以i点为终点的最长递增子序列长度,显然dp(i)=max(dp(j)+1),j<i而且j点的值比i点的值要小。

那么从哪里开始递推呢?显然dp(1)=1,第一个点作为终点,它的最长递增子序列只有它这个点。

AC代码如下:

#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

int dp[10005];
int main()
{
    int n;
    int ans=1;
    cin>>n;
    while(n--)
    {
        string s;
        cin>>s;
        ans=1;
        memset(dp,0,sizeof(dp));
        dp[0]=1;//第一个点的dp值为1 
        for(int i=1;i<s.length();i++)
        {
            for(int j=0;j<i;j++)
            {
                if(s[i]>s[j])//j点在i点之前,而且j点的值小于i点 
                {
                    dp[i]=max(dp[j]+1,dp[i]);//这就是我上面说的dp(i)=max(dp(j)+1) 
                }
                if(dp[i]>ans)//用ans记录已经求出的dp(i)的最大值 
                {
                    ans=dp[i];
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}


同样的 本题和最长递增子序列是类似的,只是从一维变成了二维,那么完全可以把二维的降为一维的,降维之后,给每个点带上二维的坐标信息,就可以用上面的方法解决。

降维之后,要按点的高度给一维数组排个序,因为点在二维数组中可以向相邻点转移,你无法知道在一维数组中哪个点在哪个点的前面,一个技巧是给一维数组按点高度从小到大排序,这样后面的点一定可以由前面的点转移而来,因为前面点的高度比后面的点要低,然后判断前面的点是否是后面点的相邻点,是的话就可以转移。

AC代码如下:

#include<iostream>
#include<algorithm>

#define row 105
#define length 10005

using namespace std;

struct node//用一维数组去存储矩阵,带上二维数组的坐标信息
{
    int x;
    int y;
    int dp;//该点(x,y)的dp(x,y)的值
    int value;//该点的高度
}maps[length];

bool cmp(const node& a, const node& b)
{
    if(a.value<b.value)
    {
        return true;
    }
    else
    {
        return false;
    }
}
int main()
{
    int r,c;
    int index=0;
    int ans=1;
    cin>>r>>c;
    for(int i=0;i<r;i++)
    {
        for(int j=0;j<c;j++)
        {
            cin>>maps[index].value;
            maps[index].x=i;
            maps[index].y=j;
            maps[index].dp=1;//初始的时候,每个点的最长路径dp(i,j)都为1
            index++;
        }
    }
    sort(maps,maps+index,cmp);
    for(int i=1;i<index;i++)
    {
        int x=maps[i].x;
        int y=maps[i].y;
        int v=maps[i].value;
        for(int j=0;j<i;j++)//j点是i点前面的点
        {
            int xx=maps[j].x;
            int yy=maps[j].y;
            int vv=maps[j].value;
            if(vv<v&&((abs(xx-x)==1&&yy==y)||(abs(yy-y)==1&&xx==x)))//j点的高度比i点低,而且j点是i点的相邻点,可以转移
            {
                if(maps[j].dp+1>maps[i].dp)//相当于我说的dp(i,j)=max(dp(i',j')+1),只是这里变成一维
                {
                    maps[i].dp=maps[j].dp+1;
                }
                if(maps[i].dp>ans)//记录已经求过的dp(i)的最大值
                {
                    ans=maps[i].dp;
                }
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/fordreamyue/article/details/73414389