动态规划入门

动态规划

一、几个要点

  1、主体思想:同一件事件不做第二次;

  2、状态表示:用问题的某些特征参数描述当前的问题;

  3、状态转移方程:

    状态值之间的递推关系(计算关系)

      边界条件  递推顺序

  4、实现方式

    自顶向下:记忆化的搜索形式。

    自底向上:递推形式。

二、可以使用动态规划的题目特点:

  1、一个大问题可以划分为若干子问题

    最优子结构

    子问题:性质一样,但是规模变小的问题。

  2、在计算时子问题有重叠

    重叠子结构

例1 、hdu2084 数塔问题

1、递归:F(x,y)=max{F(x+1,y),F(x+1,y+1)}+A[x,y];------超时

2、自下而上记忆化思想(动态规划思想)保证每一个状态最多处理一次。

//基础的DP数塔问题 
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define maxn 103
using namespace std;

int num[maxn][maxn],n,dp[maxn][maxn];
/*int f(int x,int y)    注释部分为递归超时代码 
{    
    if(x==n)
        return num[x][y];
    else        //注意这里不是num[x+1][y]和num[x+1][y+1]而是函数的递归调用。 
    {
        //return max(f(x+1,y),f(x+1,y+1))+num[x][y];//递归,保存每一层的结果,其实遍历了整个数塔; 
        res[x]=f(x,y); 
    }
}*/
int main()
{
    int t,i,j,ans;
    cin >> t ;
    while(t--)
    {
        ans=0;
        cin >> n;
        for(i=1;i<=n;i++)
            for(j=1;j<=i;j++)
                cin >> num[i][j];
        memset(dp,0,sizeof(dp));    //初始化
        for(i=1;i<=n;i++)
            dp[n][i]=num[n][i];        //数塔最后一层赋值给dp的最后一层---后面数组就不会越界。 
        for(i=n-1;i>=1;i--)            //[1-n]的数塔,从数塔的倒数第二层开始 
        {
            for(j=1;j<=i;j++)        //每层数塔 
            {                        //状态转移方程 
                dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+num[i][j];//将下一层的数中可走路径(两两之间选较大的) 加到这一层 
            }                        //记录了每一层选择的路径结果 
         }
        cout << dp[1][1] << endl;
        //ans=f(0,0);
        //cout << ans << endl; 
    }
    return 0;
}

  hdu1176 免费陷阱  http://acm.hdu.edu.cn/showproblem.php?pid=1176

/*基础dp,免费掉馅饼*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define maxn 100005    //因为0< T < 100 000 
 
using namespace std;
int dp[maxn][13];
int main()
{
    int n,i,j,s,t,m;
    while(cin >> n,n)
    {
        m=0;
        for(i=1;i<=n;i++)
        {
            cin >> s >> t;
            if(m<t)
            m=t;                //m记录下掉馅饼的最后时刻,即dp的最后一层 
            dp[t][s]++;            //每一秒路径上的情况记录下来 
        }
        for(i=m-1;i>=0;i--)        //从倒数第二层开始,是m层,不是n,n只是输入组数 
        {
            for(j=0;j<11;j++)
            {                    //每一层记录从下一层走上来的最优结果 
                 //这里的j-1其实数组越界了,但是hdu没有报错,还AC了!喵喵喵???
                dp[i][j]+=max(max(dp[i+1][j-1],dp[i+1][j]),dp[i+1][j+1]);
            }                    //状态转移方程 
        } 
        cout << dp[0][5] << endl;    //每一层的最优结果到初始位置最优得answer 
        memset(dp,0,sizeof(dp));
    }
    return 0;
 } 

 PS:上述数组越界AC了,经多方讨论,以及自爆(= =),事实证明DEV并没有报RE,CB也没有,而且越界访问的值,有的编译器是你定义的其他变量的值,也就是说越界可能会导致你其他变量的值改变(interesting),大概是分配内存时,分配的相邻了吧,但有些编译器是访问的是0 ;越界过多就会爆。

例2、最长上升子序列

1、状态定义:F[ i ]表示序列A[1……i ]的一个最长递增序列的长度(以A[ i ]结尾);

 状态转移:F[ i ] =max{F[ j ] + 1}  j<i  A[ j ] < A[ i ]

  hdu1257最少导弹拦截系统  http://acm.hdu.edu.cn/showproblem.php?pid=1257

/*最长上升子序列LIS*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define maxn 100005

using namespace std;

int dp[maxn],num[maxn];//dp[i]定义为以ai为结尾的最长上升子序列的长度
int main()
{
    int n,i,j,ans;
    //freopen("Atext.in","r",stdin);
    while(cin >> n)
    {
        ans=0;
        for(i=0;i<n;i++)
        {
            cin >> num[i];
            dp[i]=1;//每一个以ai为结尾的LIS只有两种情况,一种是他自身
        }           //另一种是它前面比它小的数的LIS加上ai
        for(i=0;i<n;i++)
        {   //dp[i]=1的赋值也可以放到这里
            for(j=0;j<i;j++)    //对每一个ai的前面走到它的路径循环记录
            {
                if(num[j]<num[i])//选出以ai结尾的最长的路径保存
                dp[i]=max(dp[j]+1,dp[i]);
            }
            ans=max(dp[i],ans); //记录各个路径的最大值,即LIS
        }
        cout << ans << endl;
    }
    return 0;
}

例3、最长公共子序列

1、状态定义:F[ i,j ]表示序列A[ 1……i ]与B[ 1……j ]的最长公共子序列长度;

  状态转移:计算F[ i , j ]时

    若A[ i ] =B[ j ]   F[ i , j ]=F[ i-1, j-1] +1;

       A[ i ] != B[ j ]  F[ i ,j ]=max{F [ i-1,j ] ,F[ i ,j -1]}

例4、背包九讲

  01背包,多重背包,完全背包……

原文地址:https://www.cnblogs.com/Cloud-king/p/8338323.html