POJ P1088动规的三种解法

Language:Default
滑雪
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 123098 Accepted: 47211

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

SHTSC 2002

解法一:记忆化搜索

其实就是dfs,对每一个点都进行搜索一遍,求出每个点的最长滑雪长度,这样在每一个点中遍历找出最大的值,就是最长的滑雪长度,

但是这样做虽然好理解,有个致命的缺点,就是有很多点每一次在dfs的过程中都会访问到,这样就导致大量的重复计算,所以时间复杂度太高,交上去会超时,但是这是我们理解这道题目的很好的入口。

用一个公式简单地说:记忆化搜索=搜索的形式+动态规划的思想。
动态规划:就是一个最优化问题,先将问题分解为子问题,并且对于这些分解的子问题自身就是最优的才能在这个基础上得出我们要解决的问题的最优方案,要不然的话就能找到一个更优的解来替代这个解,得出新的最优自问题,这当然是和前提是矛盾的。动态规划不同于 贪心算法,因为贪心算法是从局部最优来解决问题,而动态规划是全局最优的。用动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,而是必须等待子问题得到了最优解之后才对当下的情况做出决策,所以往往动态规划都可以用 一个或多个递归式来描述。而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。
一般遇到一个动态规划类型的问题,都先要确定最优子结构,还有重叠子问题,这两个是动态规划最大的特征,然后就是要写 动态规划的状态方程,这个步骤十分十分的重要的,写动归方程是需要一定的经验的,这可以通过训练来达到目的。接着就是要自底向上的求解问题的,先将最小规模的子问题的最优解求出,一般都用一张表来记录下求得的解,到后来遇到同样的子问题的时候就可以直接查表得到答案,最后就是通过一步一步的迭代得出最后问题的答案了。
我的理解最重要的东西就是一定会要一个数组或者其他的存储结构存储得到的子问题的解。这样就可以省很多时间,也就是典型的空间换时间
动态规划的一种变形就是记忆化搜索,就是根据动归方程写出递归式,然后在函数的开头直接返回以前计算过的结果,当然这样做也需要一个存储结构记下前面计算过的结果,所以又称为记忆化搜索。
记忆化搜索递归式动态规划
1.记忆化搜索的思想
记忆化搜索的思想是,在搜索过程中,会有很多重复计算,如果我们能记录一些状态的答案,就可以减少重复搜索量
2、记忆化搜索的适用范围
根据记忆化搜索的思想,它是解决重复计算,而不是重复生成,也就是说,这些搜索必须是在搜索扩展路径的过程中分步计算的题目,也就是“搜索答案与路径相关”的题目,而不能是搜索一个路径之后才能进行计算的题目,必须要分步计算,并且搜索过程中,一个搜索结果必须可以建立在同类型问题的结果上,也就是类似于动态规划解决的那种。
也就是说,他的问题表达,不是单纯生成一个走步方案,而是生成一个走步方案的代价等,而且每走一步,在搜索树/图中生成一个新状态,都可以精确计算出到此为止的费用,也就是,可以分步计算,这样才可以套用已经得到的答案
3、记忆化搜索的核心实现
    a. 首先,要通过一个表记录已经存储下的搜索结果,一般用哈希表实现
    b.状态表示,由于是要用哈希表实现,所以状态最好可以用数字表示,常用的方法是把一个状态连写成一个p进制数字,然后把这个数字对应的十进制数字作为状态
    c.在每一状态搜索的开始,高效的使用哈希表搜索这个状态是否出现过,如果已经做过,直接调用答案,回溯  
    d.如果没有,则按正常方法搜索
4、记忆化搜索是类似于动态规划的,不同的是,它是倒做的“递归式动态规划”。

这道题我要写的第一种写法就是记忆化搜索

应为你要求最长的滑雪坡的长度   求一个最长的下降序列,并且 你只能向 上下左右走,只能走更低的位置,这样的话,我们搜索每一个点能够下降的最长的长度,我们就可以用递归DFS去搜索,直到搜到边界返回(边界就是上下左右都不能走,然后自己的值是 1 ),但是这样搜索一定会超时,因为我们进行了太多的重复搜索。因为,当这个位置已经被搜过后,那这个点就是此时能够到最低点的最长的坡,当我们下次搜索时,还会进去继续搜索,所以记忆化搜索就出现了,我们用一个数组来保存当我们搜索过的值,就可以避免重复搜索,每当我们再搜索到这里时,我们直接用它已经搜索过的值即可。

代码如下:

#include
#include
#include
#include
#include

typedef long long ll;
using namespace std;

//记忆化搜索
int to[4][2] ={{1,0},{-1,0},{0,1},{0,-1} };
int n,m;
int high[105][105];
int maxlen[105][105];

bool check(int x,int y)
{

    if(x>=1 && y>=1 && x<=n && y<=m)
    {
        return 1;
    }
    else
        return 0;
}

dfs(int i,int j)
{
    if(maxlen[i][j] != 0)
    {//被访问过,直接返回
        return maxlen[i][j];
    }

    maxlen[i][j] = 1;

    for(int k=0;k<4;k++)
    {
        int x1 = i + to[k][0];
        int y1 = j + to[k][1];
        if(check(x1,y1) && high[x1][y1]>n>>m;

    int ans = 1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>high[i][j];
            maxlen[i][j] = 0;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            ans = max(ans,dfs(i,j));
        }
    }
    cout<

解法二:动态规划——我为人人型

简单的说就是在计算出某个点的最长滑雪长度后,就可以沿着这个点遍历四周的四个点,这样就可以一直延伸下去,而不用重复的计算某些点。

操作步骤需要从低到高计算,这样就可以保证每次得到的都是比这个点低的最长长度

步骤如下:

第一步,我们需要将所有的点按 递增序列排序。

第二步,我们从最低点更新上下左右的4个点,直到每给个点都用了。

第三步,输出我们找的过程中的最大值。

代码如下:

#include
#include
#include
#include
#include

typedef long long ll;
using namespace std;


//我为人人型
int to[4][2] ={{1,0},{-1,0},{0,1},{0,-1} };
int n,m;
int high[105][105];
int maxlen[105][105];

int max1 = 1;
struct node{
    int x;
    int y;
    int data;
};

bool cmp(node a,node b)
{
    return a.data < b.data;
}
bool check(int x,int y)
{

    if(x>=1 && y>=1 && x<=n && y<=m)
    {
        return 1;
    }
    else
        return 0;
}

void maxl(node s[],int index)
{
    int x,y;
    x = s[index].x;
    y = s[index].y;
    for(int i=0;i<4;i++)
    {
        int x1 = x + to[i][0];
        int y1 = y + to[i][1];
        if(check(x1,y1) && high[x1][y1]>high[x][y] && maxlen[x1][y1]>n>>m;
    node s[10005];
    int k=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>high[i][j];
            s[++k].x = i;
            s[k].y = j;
            s[k].data = high[i][j];
            maxlen[i][j] = 1;
        }
    }
    sort(s+1,s+k+1,cmp);
    for(int i=1;i<=k;i++)
    {
        maxl(s,i);
    }

    cout<

解法三:动态规划——人人为我型

也就是每次从周围的四个点推出我这个点的最大高度。

这种给记忆化搜索差不多,你的目的是求每一个结点的最长升子序列,那你这个点的值取决什么呢?  是不是取决与他上下左右4个点的最长上升子序列,那用递归就好写了,但是把他转化成动态规划,这个思路就是找到状态方程,那我们把这个问题分解,就是求每个点的上升子序列,那我们找到边界,就是最低的那个点。这个人人为我型和我为人人型的唯一不一样就是这里,我为人人型 是用这个点更新其他4个点,而人人为我型就是用周围4个点更新这个一个点。找到一个周围4个点没法更新他的,不就是最低的那个点么?我们就想出了这个状态方程,从边界往上推,直到推到最后一个点(最高的点)

第一步,先进行点排序递增排序。

第二步,找到最小的点,用上下左右的点更新他,然后次小,一直到最大,(排完序后一个循环就OK)

第三步,找的过程中记录每个点的状态,并找到最大值,
代码如下:

#include
#include
#include
#include
#include

typedef long long ll;
using namespace std;

//人人为我型
int n,m;
int to[4][2] ={{1,0},{-1,0},{0,1},{0,-1}};
struct node{
    int x;
    int y;
    int data;
};

bool cmp(node a,node b)
{
    return a.data =1 && y>=1 && x<=n && y<=m)
    {
        return 1;
    }
    else
        return 0;
}

int main()
{
    int high[105][105];
    int maxlen[105][105];
    node s[10005];
    int k = 0;
    int ans  = 1;
    cin>>n>>m;
    for(int i=1;i<=n ;i++)
    {

        for(int j=1;j<=m;j++)
        {
            cin>>high[i][j];
            s[++k].x = i;
            s[k].y = j;
            s[k].data = high[i][j];
            maxlen[i][j] = 1;
        }
    }

    sort(s+1,s+1+k,cmp);
    for(int i=1;i<=k;i++)
    {
        for(int j=0;j<4;j++)
        {
            int x1 = s[i].x + to[j][0];
            int y1 = s[i].y + to[i][1];
            if(check(x1,y1) && high[s[i].x][s[i].y]>high[x1][y1])
            {
                maxlen[s[i].x][s[i].y] = max(maxlen[s[i].x][s[i].y],maxlen[x1][y1]+1);
                ans = max(ans,maxlen[s[i].x][s[i].y]);
            }
        }
    }
    cout<

你可能感兴趣的:(学习经验分享,动态规划)