动态规划典型例题详解(最长公共子序列(POJ1458), Help Jimmy(POJ1661))

例题1:最长公共子序列(POJ1458)
给出两个字符串,求出这样的一 个最长的公共子序列的长度:子序列 中的每个字符都能在两个原串中找到, 而且每个字符的先后顺序和原串中的 先后顺序一致。
输入
abcfbc abfcab
programming contest
abcd mnp
输出
4
2
0

解题思路:
遇见动归问题,第一步需要考虑状态是什么。
输入两个串s1,s2
设MaxLen(i,j)表示:s1的左边i个字符形成的子串,与s2左边的j个 字符形成的子串的最长公共子序列的长度(i,j从0 开始算),则MaxLen(i,j) 就是本题的“状态”
第二步,就是动用小脑子了,考虑下可能的情况还有递推公式。
对这道题来说
MaxLen(n,0) = 0(这里的n是属于s1的)
MaxLen(0,n) = 0(这里的n是属于s2的)

递推公式:
if(s1[i - 1] == s2[j - 1])
MaxLen(i,j) = MaxLen(i - 1,j - 1) + 1;
else
MaxLen(i,j) = Max(MaxLen(i,j - 1),MaxLen(i - 1,j));
注:s1[i - 1] != s2[j - 1]时,MaxLen(s1,s2)不会比MaxLen(s1,s2j - 1)和MaxLen(s1i - 1,s2)两者之中任何一个小,也不会比两者都大。

时间复杂度是O(m,n),m,n是两个字串长度。

#include<iostream>
#include<cstring>
using namespace std;
string sz1;
string sz2;
int maxLen[1000][1000];

int main()
{
    while(cin >> sz1 >> sz2)
    {
        int length1 = sz1.length();
        int length2 = sz2.length();
        int i,j;
        for(i = 0 ; i <= length1 ; i++)
            maxLen[i][0] = 0;
        for(j = 0 ; j <= length2 ; j++)
            maxLen[0][j] = 0;
        
        for(i = 1 ; i <= length1 ; i++)
        {
            for(j = 1 ; j <= length2 ; j++)
            {
                if(sz1[i - 1] == sz2[j - 1])
                    maxLen[i][j] = maxLen[i - 1][j - 1] + 1;
                else
                    maxLen[i][j] = max(maxLen[i][j - 1],maxLen[i - 1][j]);
            }
        }
        cout << maxLen[length1][length2] << endl;
    }
    return 0;
}

例二: Help Jimmy(POJ1661)
“Help Jimmy” 是在下图所示的场景上 完成的游戏:
这里写图片描述

场景中包括多个长度和高度各不相同的平台。 地面是最低的平台,高度为零,长度无限。 Jimmy老鼠在时刻0从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度 也是1米/秒。当Jimmy跑到平台的边缘时,开始继续下 落。Jimmy每次下落的高度不能超过MAX米,不然就 会摔死,游戏也会结束。

设计一个程序,计算Jimmy到地面时可能的最早时间。

输入数据
第一行是测试数据的组数t(0 <= t <= 20)。每组测试数据的第一行是四 个整数N,X,Y,MAX,用空格分隔。N是平台的数目(不包括地面),X和Y是 Jimmy开始下落的位置的横竖坐标,MAX是一次下落的最大高度。接下来的N行 每行描述一个平台,包括三个整数,X1[i],X2[i]和H[i]。H[i]表示平台的 高度,X1[i]和X2[i]表示平台左右端点的横坐标。1 <= N <= 1000,-20000 <= X, X1[i], X2[i] <= 20000,0 < H[i] < Y <= 20000(i = 1…N)。所 有坐标的单位都是米。
Jimmy的大小和平台的厚度均忽略不计。如果Jimmy恰好落在某个平台的边 缘,被视为落在平台上。所有的平台均不重叠或相连。测试数据保Jimmy一定
能安全到达地面。
输出要求
对输入的每组测试数据,输出一个整数, Jimmy到地面时可能的最早时间。 输入样例
1
3 8 17 20 0 10 8
0 10 13
4 14 3
输出样例 23

解题思路:
Jimmy跳到一块板上后,可以有两种选择,向左走,或向右走。 走到左端和走到右端所需的时间,是很容易算的。 如果能知道,以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左走还是向右走,就很容易选择了。 因此,整个问题就被分解成两个子问题,即Jimmy所在位置下方第一块板左端 为起点到地面的最短时间,和右端为起点到地面的最短时间。 这两个子问题在形式上和原问题是完全一致的。将板子从上到下从1开始进行 无重复的编号(越高的板子编号越小,高度相同的几块板子,哪块编号在前无所谓),那么,和上面两个子问题相关的变量就只有板子的编号了。
设Jimmy开始的位置是一个编号为0,长度为0的板子,假设LeftMinTime(k)表示从k号板子左端到地面的最短时间, RightMinTime(k)表示从k号板子右端到地面的最短时间,那么, 求板子k左端点到地面的最短时间的方法如下:
if ( 板子k左端正下方没有别的板子)
{
if( 板子k的高度 h(k) 大于Max)
LeftMinTime(k) = ∞;
else
LeftMinTime(k) = h(k);
}
else if( 板子k左端正下方的板子编号是m )
LeftMinTime(k) = h(k)-h(m) + Min( LeftMinTime(m) + Lx(k)-Lx(m),RightMinTime(m) + Rx(m)-Lx(k));
}

把Jimmy开始的位置当成是编号为0,长 度为0的板子,那么整个问题就是要求 LeftMinTime(0)。
输入数据中,板子并没有按高度排序,所以程 序中一定要首先将板子排序。

时间复杂度:
一共 n个板子,每个左右两端的最小时间各算 一次 O(n)
找出板子一段到地面之间有那块板子,需要遍 历板子 O(n)
总的时间复杂度O(n2)

// 记忆递归的程序:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAX_N 1000
#define INFINITE 1000000
int t,n,x,y,maxHeight;
struct Platform
{
    int Lx, Rx, h;
    bool operator < (const Platform & p2) const
    {
        return h > p2.h;
        
    }
};

Platform platForms[MAX_N + 10];
int leftMinTime[MAX_N + 10];
int rightMinTime[MAX_N + 10];
int L[MAX_N + 10];
int MinTime( int l, bool bLeft )
{
    int y = platForms[l].h;
    int x;
    if( bLeft )
        x = platForms[l].Lx;
    else
        x = platForms[l].Rx;
    int i;
    for( i = l + 1;i <= n;i ++ )
    {
        if( platForms[i].Lx <= x && platForms[i].Rx >= x)
            break;
    }
    if(i <= n)
    {
        if( y - platForms[i].h > maxHeight )
            return INFINITE;
    }
    else
    {
        if(y > maxHeight)
            return INFINITE;
        else
            return y;
    }
    
  
    int nLeftTime = y - platForms[i].h + x - platForms[i].Lx;
    int nRightTime = y - platForms[i].h + platForms[i].Rx - x; if( leftMinTime[i] == -1 )
        leftMinTime[i] = MinTime(i,true);
        if( L[i] == -1 )
            L[i] = MinTime(i,false);
    nLeftTime += leftMinTime[i];
    nRightTime += L[i];
    if( nLeftTime < nRightTime )
        return nLeftTime;
    return nRightTime;
}

int main()
{
    scanf("%d",&t);
    for( int i = 0;i < t; i ++ )
    {
        memset(leftMinTime,-1,sizeof(leftMinTime)); memset(L,-1,sizeof(rightMinTime));
        scanf("%d%d%d%d",&n, &x, &y, &maxHeight);
        platForms[0].Lx = x;
        platForms[0].Rx = x;
        platForms[0].h = y;
        for( int j = 1; j <= n; j ++ )
            scanf("%d%d%d",&platForms[j].Lx,& platForms[j].Rx, & platForms[j].h);
        sort(platForms,platForms + n + 1);
        printf("%d\n", MinTime(0,true));
    }
    return 0;
}

//递推的程序:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAX_N 1000
#define INFINITE 1000000
int t,n,x,y,maxHeight;
struct Platform
{
    int Lx, Rx, h;
    bool operator < (const Platform & p2) const
    {
        return h > p2.h;
        
    }
};


Platform platforms[MAX_N + 10];
int leftMinTime[MAX_N + 10]; //各板子从左走最短时间
int rightMinTime[MAX_N + 10]; //各板子从右走最短时间
int main()
{
    scanf("%d",&t);
    while( t--)
    {
        scanf("%d%d%d%d",&n, &x, &y, &maxHeight);
        platforms[0].Lx = x; platforms[0].Rx = x; platforms[0].h = y; for( int j = 1; j <= n; j ++ )
            scanf("%d%d%d",&platforms[j].Lx,& platforms[j].Rx, & platforms[j].h);
            sort(platforms,platforms+n+1);
            
            for( int i = n ; i >= 0; -- i )
            {
                int j;
                for( j = i + 1; j <= n ; ++ j )
                { //找i的左端的下面那块板子
                    if( platforms[i].Lx <= platforms[j].Rx
                    && platforms[i].Lx >= platforms[j].Lx)
                    break;
                }
                if( j > n )
                { //找不到
                
                if( platforms[i].h > maxHeight )
                    leftMinTime[i] = INFINITE;
                    else
                        leftMinTime[i] = platforms[i].h;
                }
                        
                        else
                        {
                            int y = platforms[i].h - platforms[j].h;
                            if( y > maxHeight)
                                leftMinTime[i] = INFINITE;
                                else
                                    leftMinTime[i] = y + min(leftMinTime[j]+platforms[i].Lx-platforms[j].Lx, rightMinTime[j]+platforms[j].Rx-platforms[i].Lx);
                        }
                for( j = i + 1; j <= n ; ++ j )
                { //找i的右端的下面那块板子
                
                if( platforms[i].Rx <= platforms[j].Rx
                   && platforms[i].Rx >= platforms[j].Lx)
                    break;
                }
                
                if( j > n )
                {
                    if( platforms[i].h > maxHeight )
                        rightMinTime[i] = INFINITE;
                    else rightMinTime[i] = platforms[i].h;
                }
                else {
                    int y = platforms[i].h - platforms[j].h;
                    if( y > maxHeight) rightMinTime[i] = INFINITE;
                    else
                        rightMinTime[i] = y + min(leftMinTime[j]+platforms[i].Rx-platforms[j].Lx,
                                                  rightMinTime[j]+platforms[j].Rx-platforms[i].Rx);
                    
                            }
                
            }
        printf("%d\n", min(leftMinTime[0],rightMinTime[0]));
        
    }
    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Αиcíеиτеǎг

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

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

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

打赏作者

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

抵扣说明:

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

余额充值