• 第一周 动态规划Dynamic Programming(一)


    一、概念

      动态规划是运筹学的一个分支,是求解决策过程最优化的数学方法。动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

    1、试用情况:

    2、解决步骤:

      1、拆分问题

      2、找状态(初始值)

      3、状态转移方程

    3、DP问题的分类:

      1、线性dp  2、背包  3、区间dp  4、数位dp  5、状压dp  6、树形dp  7、概率dp

    4、具体典例:

    A线性DP:

       最长上升子序列(LIS)

    /*最长上升子序列LIS---hdu1257*/
    #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;
            /*O(nlogn)的方式--利用二分查找
            dp[i]:长度为i+1的上升子序列中末尾元素的最小值(不存在的话就是INF); 
            fill(dp,dp+n,INF);
            for(int i=0;i<n;i++)
            {
                *lower_bound(dp,dp+n,a[i])=a[i];//找到>=a[i]的第一个元素,并用a[i]替换;
            }
            cout<<lower_bound(dp,dp+n,INF)-dp<<endl;//找到第一个INF的地址减去首地址就是最大子序列的长度;
            */
        }
        return 0;
    }

       最长公共序列(LCS)

      

    /*LCS----最长公共子序列*/
    #include <bits/stdc++.h>
    
    #define maxn 1005 
    using namespace std;
    char s[maxn],t[maxn];    //待判断的字符串数组 
    int dp[maxn][maxn];        //si与tj对应的公共子序列的长度 
    int main()
    {
        int i,j,n,m;
        cin >> n >> m;
        for(i=0;i<n;i++)
            cin >> s[i];
        for(j=0;j<m;j++)
            cin >> t[j]; 
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
            {
                if(s[i]==t[j])
                dp[i+1][j+1]=dp[i][j]+1;
                else
                dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
            }
         } 
         cout << dp[n][m] << endl;
        return 0;
     } 
    //自我心得:感觉无论是01背包还是LCS的二维数组都记录了每一种可能组合的状态,并且是该组合状态下的最优化值 
    //通过记录每一步状态的转移,一步步递推出最终的结果。 

     B.背包(有背包九讲):

      01背包

      

    /*01背包(递归版)*/
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define maxn 1005 
    using namespace std;
    
    int n,W;    //int dp[n][j]; 第i个物体,背包容量为j时的价值 
    int w[maxn],v[maxn]; 
    int res(int i,int j)    //第i个物体,背包剩余容量j; 
    {
        /*if(dp[i][j]>=0)        记忆化搜索,每种情况只计算一次 
        return dp[i][j];*/
        int ans;            //背包里的总价值 
        if(i==n)            //i个物体取或不取得情况都试完了; 
        ans=0;
        else if(j<w[i])        //此物体的重量大于背包容量,一定不能取,直接下一个 
        ans=res(i+1,j);
        else 
        {    //取或不取的价值--递归调用 
            ans=max(res(i+1,j),res(i+1,j-w[i])+v[i]);
        } 
        //dp[i][j]=ans;参数的组合只有n*W种,计算过的组合就存起来 
        return ans; 
    }
    int main()
    {
        int i,j;
        //memset(dp,-1,sizeof(dp));
        cin >> n >> W;    //n个物体,背包容量为W 
        for(i=0;i<n;i++)
            cin >> w[i] >> v[i] ;//输入每个物体的容量和价值 
        cout << res(0,W) << endl;//从第i个物体开始,挑选总重小于等于j的部分; 
        return 0;    
    } 
    /*01背包(普通版)*/
    #include <bits/stdc++.h>
    #define maxn 100
    using namespace std;
    
    int w[maxn],v[maxn];//n个物体的重量及价值 
    int dp[maxn][maxn];    //前i个物体在背包容量为j的情况下的价值的最大值 
    int main()
    {
        int n,W,i,j; 
        cin >> n >> W;
        memset(dp,0,sizeof(dp));
        for(i=0;i<n;i++)
            cin >> w[i];
        for(j=0;j<n;j++)
            cin >> v[j];
        for(i=0;i<n;i++)    //无论从前往后递推还是从后往前递推,其实都是记录所有的状态 
        {
            for(j=0;j<=W;j++)
            {
                if(w[i]>j)
                    dp[i+1][j]=dp[i][j];    //dp[i+1][j]:从0到i这i+1个物体中选出总重量不超过j的物体时总价值的最大值 
                else
                    dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
            }
        }/*01 背包循环利用单数组实现 
        for(i=0;i<n;i++)
        {
            for(j=W;j>=W[i];j--)    //循环利用一个数组,只记录前i个物体在背包的各种状态下的最优值。 
            {                    //即dp数组在背包的各个容量下的最优值。 
                dp[j]=max(dp[j],dp[j-W[i]]+v[i]);
            }
         } */
        cout << dp[n][W] << endl;
        return 0;    
    } 
    //自我心得:n个物体与j容量的背包,组合情况有n*j种,dp二维数组其实就是记录每一种状态下的最优化的值;
    //然后通过状态转移方程对状态一步步将结果递推转移出来; 

          完全背包

        

    #include <bits/stdc++.h>
    #define maxn 100 
    /*完全背包*/
    using namespace std;
    int dp[maxn][maxn];
    int w[maxn],v[maxn];
    int main()
    {
        int n,W,i,j;
        cin >> n >> W;
        for(i=0;i<n;i++)
        cin >> w[i];
        for(i=0;i<n;i++)
        cin >> v[i];
        for(i=0;i<n;i++)
        {
            for(j=0;j<=W;j++)
            {
                if(j<w[i])
                dp[i+1][j]=dp[i][j];
                else    //在dp[i+1][j]的计算中选择k(k>=1)个的情况,与在dp[j+1][j-W[i]]的计算中选择k-1个的情况是相同的。 
                dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
            }
        }/*
        for(int i=0;i<n;i++)
        {
            for(int j=w[i];j<=W;j++)//针对背包容量dp,只存最优值。 
            {
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
         } */
        cout << dp[n][W] << endl;
        return 0;
    }
    //自我心得:感觉单数组就是针对每一种背包容量情况,循环n个物体,将dp数组里不断地存入最优化的值 
    //PS:还可利用滚动数组,当数据限制改变也可用DP针对不同的价值计算最小的重量
    //如:dp[i+1][j]:前i个物体中挑出价值总和为j时总重量的最小值。 

     数位dp及状压dp见(二)……

  • 相关阅读:
    软件测试的几种基本方法
    什么是软件测试及软件测试基本原则
    HTTP状态码大全
    jsp 九大内置对象和其作用详解
    快速搞定常用的ES6新特性
    javascript 闭包的学习
    js 中location 的学习
    js 中事件的学习
    js 小菜鸟的学习
    mongodb的返回(3)
  • 原文地址:https://www.cnblogs.com/Cloud-king/p/8375948.html
Copyright © 2020-2023  润新知