动态规划(1) 数字三角形 最长上升子序列 最长公共子序列

1、记忆化数组
2.、动态规划思想
递归定义-> 递推公式
思路 先走一步,看剩下的
递推公式 初值 递归出口
递推公式 递归体
一、数字三角形(POJ1163)
在上面的数字三角形中寻找一条从顶部到底边的路径,使得
路径上所经过的数字之和最大。路径上的每一步都只能往左下或
右下走。只需要求出这个最大和即可,不必给出具体路径。
三角形的行数大于1小于等于100,数字为 0 - 99
Sample Input
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30

解题思路:
用二维数组存放数字三角形。
D( r, j) : 第r行第 j 个数字(r,j从1开始算)
MaxSum(r, j) : 从D(r,j)到底边的各条路径中,
最佳路径的数字之和。
问题:求 MaxSum(1,1)
典型的递归问题。
D(r, j)出发,下一步只能走D(r+1,j)或者D(r+1, j+1)。故对于N行的三角形:
if ( r == N)
MaxSum(r,j) = D(r,j)
else
MaxSum( r, j) = Max{ MaxSum(r +1,j), MaxSum(r+1,j+1) }
+ D(r,j) 递归,由递归推出递推公式

1、可用记忆数组 先将数组内全赋值-1,对于有了的数,改变数值存储,在后续再次用到时,可以直接用
2、递归转成递推
“人人为我”递推型动归程序 设一个数组维度与变量的参数个数有关

#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <cstdio>//在c++中一些c的东西,如输入输出之类的
using namespace std;
int main()
{
    int i,j;
    int n;
    int a[105][105];
    scanf("%d",&n);
    for (i=1;i<=n;i++)
    {
        for (j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    }
    int maxa[105][105];
    for(j=1;j<=n;j++)
        maxa[n][j]=a[n][j];//最后一行存进去
    for (i=n-1;i>=0;i--)//课件上是i>=1,无所谓
    {
        for (j=1;j<=i+1;j++)
        maxa[i][j]=max(maxa[i+1][j],maxa[i+1][j+1])+a[i][j];
    }//“人人为我”递推型动归程序
    //从n-1行开始这个位置的下一行的j,或是j+1,选择最大的,再与当前的数相加
    printf("%d",maxa[1][1]);//到最后是第一行,只留下这一个
    system("pause");
    return 0;
}

优化空间 可用一个一维数组来存储 ,把算完之后的数,直接存在原来不参与刚才加的数的位置


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <stdlib.h>
using namespace std;
int *maxa;//一个指针
int n;
int  main()
{
    int a[105][105];
    int i,j;
    cin>>n;
    for (i=1;i<=n;i++)
        for (j=1;j<=i;j++)
            cin>>a[i][j];
    maxa=a[n];//将maxa指向第n行,相当于将maxa变成了一个一维数组
    for (i=n-1;i>0;i--)
    {
        for(j=1;j<=i;j++)
            maxa[j]=max(maxa[j],maxa[j+1])+a[i][j];//和刚才的类似
    }
    cout<<maxa[1]<<endl;
    system("pause");
    return 0;
}

递归到动规的一般转化方法
递归函数有n个参数,就定义一个n维的数组,数组
的下标是递归函数参数的取值范围,数组元素的值
是递归函数的返回值,这样就可以从边界值开始,
逐步填充数组,相当于计算递归函数值的逆过程。
动规解题的一般思路
1. 将原问题分解为子问题
把原问题分解为若干个子问题,子问题和原问题形式相同
或类似,只不过规模变小了。子问题都解决,原问题即解
决(数字三角形例)。
子问题的解一旦求出就会被保存,所以每个子问题只需求
解一次。
2. 确定状态
在用动态规划解题时,我们往往将和子问题相
关的各个变量的一组取值,称之为一个“状
态”。一个“状态”对应于一个或多个子问题,
所谓某个“状态”下的“值”,就是这个“状
态”所对应的子问题的解。
所有“状态”的集合,构成问题的“状态空间”。“状态
空间”的大小,与用动态规划解决问题的时间复杂度直接相关。
在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个
问题的状态空间里一共就有N×(N+1)/2个状态。
整个问题的时间复杂度是状态数目乘以计算每个状态所需
时间。
在数字三角形里每个“状态”只需要经过一次,且在每个
状态上作计算所花的时间都是和N无关的常数。
用动态规划解题,经常碰到的情况是,K个整型变量能
构成一个状态(如数字三角形中的行号和列号这两个变量
构成“状态”)。如果这K个整型变量的取值范围分别是
N1, N2, ……Nk,那么,我们就可以用一个K维的数组
array[N1] [N2]……[Nk]来存储各个状态的“值”。这个
“值”未必就是一个整数或浮点数,可能是需要一个结构
才能表示的,那么array就可以是一个结构数组。一个
“状态”下的“值”通常会是一个或多个子问题的解。
3. 确定一些初始状态(边界状态)的值
以“数字三角形”为例,初始状态就是底边数字,值
就是底边数字值。
4. 确定状态转移方程
定义出什么是“状态”,以及在该 “状态”下的“值”后,就要
找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的
“状态”,求出另一个“状态”的“值”(“人人为我”递推型)。状
态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方
程”。
能用动规解决的问题的特点
1) 问题具有最优子结构性质。如果问题的最优解所包含的
子问题的解也是最优的,我们就称该问题具有最优子结
构性质。
2) 无后效性。当前的若干个状态值一旦确定,则此后过程
的演变就只和这若干个状态的值有关,和之前是采取哪
种手段或经过哪条路径演变到当前的这若干个状态,没
有关系。
二、最长上升子序列(百练2757)
问题描述
一个数的序列ai,当a 1 < a 2 < … < a S 的时候,我们称这个
序列是上升的。对于给定的一个序列(a 1 , a 2 , …, a N ),我们可
以得到一些上升的子序列(a i1 , a i2 , …, a iK ),这里1 <= i1 <
i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),
有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序
列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出
最长上升子序列的长度。
样例输入
7
1 7 3 5 9 4 8
样例输出
4

#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
int main()
{
    int n;
    int i,j,k=2;
    cin>>n;
    int a[10005];
    int m[10005];
    for(i=1;i<=n;i++)
    {
        cin>>a[i];m[i]=1;//赋值为1,长度至少为1
    }
    for (i=2;i<=n;i++)
    {//以第i个数为终点算长度
        for(j=1;j<i;j++)
        {//i数和钱j个数比较,算长度
            if (a[i]>a[j])
                m[i]=max(m[i],m[j]+1);
        }
    }
    cout<<* max_element(m+1,m+n+1);//将数组中的最大的输出
    //sort(m+1,m+n,cmp);
    //cout<<m[1]<<endl;//本来想排序后输出,可能得按照把数组从0开始放的话
    system("pause");
    return 0;
}

”人人为我”递推型动归 (图的入)
状态i的值F i 由若干个值
已知的状态值F k ,F m ,..F y
推出,如求和,取最大值
动归的三种形式

“我为人人”递推型动归(图的出)
状态i的值F i 在被更新(不一定是
最终求出)的时候,依据F i 去更
新(不一定是最终求出)和状态i
相关的其他一些状态的值
F k ,F m ,..F y

1)记忆递归型
优点:只经过有用的状态,没有浪费。递推型会查看一些
没用的状态,有浪费
缺点:可能会因递归层数太深导致爆栈,函数调用带来额
外时间开销。无法使用滚动数组节省空间。总体来说,比递推
型慢。
2) “我为人人”递推型
没有什么明显的优势,有时比较符合思考的习惯。个别特
殊题目中会比“人人为我”型节省空间。
3) “人人为我”递推型
状态i的值F i 由若干个值
已知的状态值F k ,F m ,..F y
推出,如求和,取最大值。在选取最优备选状态的值Fm,Fn,…Fy时,
有可能有好的算法或数据结构可以用来显
著降低时间复杂度。

三、 最长公共子序列(POJ1458)
给出两个字符串,求出这样的一
个最长的公共子序列的长度:子序列
中的每个字符都能在两个原串中找到,
而且每个字符的先后顺序和原串中的
先后顺序一致。

Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0

#include <iostream>
#include <cstring>
#include <cstdio>
char s1[1005];
char s2[1005];//数组的大小不能太大
using namespace std;
int main()
{
    while(cin>>s1>>s2)
    {
        int i,j;
        int l1,l2;
        int ma[1005][1005];//相当于将两个分别存于一整行,一整列上
        l1=strlen(s1);
        l2=strlen(s2);
        for (i=0;i<=l1;i++)
            ma[i][0]=0;//将第一列整个为0
        for (j=0;j<=l2;j++)
            ma[0][j]=0;//将第一行为0
        for (i=1;i<=l1;i++)
        {
            for (j=1;j<=l2;j++)
            {//相等前面的长度+1
                if (s1[i-1]==s2[j-1]) 
                    ma[i][j]=ma[i-1][j-1]+1;
                else 
                {//不相等,看s1的前一个字母,再看s2的前一个字母,找最大
                    ma[i][j]=max(ma[i-1][j],ma[i][j-1]);
                }
            }
        }
        cout<<ma[l1][l2]<<endl;//最后的存下的长度
    } 

    return 0;
}

收集了很多北大郭炜老师的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值