动态规划问题总结

一、
鉴别:题目里有最优、最长、最大、最小、计数等字眼,要考虑是否是DP问题

二、解决思路
将原问题拆解成子问题

  1. 所有的动归问题基本都可以使用递归求解(想象斐波那契数列),寻找递推公式,这里的设计要考虑无效性,即如果我走了当前这一步,会造成什么后果(比如下一步不能走)。
  2. 但是利用递归会出现大量的冗余计算,这时要想着怎么优化问题——去冗余(一般问题都是离散的,这样把之前计算的结果保存到数组、二维数组、map等数据结构中,就可以防止重复计算)
  3. 递归是自顶向下计算,从n推到0,可以使用循环(自底向上)从0推到N设计,避免递归。

三、经典题

  • 斐波那契数列(上台阶)
  • 机器人在棋盘中的路径
#include <iostream>
#include <vector>
using namespace std;
int uniquePaths(int m, int n);
int main()
    {
        int a=2,b=4;
       int r= uniquePaths(a,b);
        cout<<r<<endl;
    }

int uniquePaths(int m, int n)
{
    int res[m][n];
    res[0][0]=1;
    for(int i=0;i<m;i++)
        res[i][0]=1;
    for(int i=1;i<n;i++)
    {
        res[0][i]=1;
    }
    for(int i=1;i<m;i++)
    {
        for(int j=1;j<n;j++)
        {
            res[i][j]=res[i-1][j]+res[i][j-1];
            if(j==1d&&i==1)
                res[i][j]=0;
        }
    }
    return res[m-1][n-1];
}
  • 排列组合
  • 01背包
#include<iostream>
using namespace std;


int main()
{
    int N,V;
    cin>>N>>V;
    int weight[N];
    int vaule[N];
    for(int i=1;i<=N;i++)
      cin>>weight[i]>>vaule[i];
    int f[N+1][V+1];
    for(int i=0;i<=N;i++)
    {
        for(int j=0;j<=V;j++)
        {
            f[i][j]=0;
        }
    }
    for(int i=1;i<=N;i++)
    {
        for(int j=1;j<=V;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=weight[i])
            f[i][j]=max(f[i][j],f[i-1][j-weight[i]]+vaule[i]);
        }
        
    }
    cout<<f[N][V];
}
   //空间复杂度N*V,时间复杂度N*V
   
/****************************************************************/
优化空间复杂度的办法,用一个一维数组存储状态 (通用解法,使用滚动数组)
#include<iostream>
using namespace std;


int main()
{
    int N,V;
    cin>>N>>V;
    int weight[N];
    int vaule[N];
    for(int i=1;i<=N;i++)
      cin>>weight[i]>>vaule[i];
    int f[V+1];
        for(int j=0;j<=V;j++)
        {
            f[j]=0;
        }
    
    for(int i=1;i<=N;i++)
    {
        for(int j=V;j>=weight[i];j--)
        {
            f[j]=max(f[j],f[j-weight[i]]+vaule[i]); 改成一维数组
        }
        
    }
    cout<<f[V];
}

  • 完全背包问题(leedcode322)
    在这里插入图片描述
    完全背包问题有一个套路魔板
int main()
{
    int N,V;
    cin>>N>>V;
    int weight[N];
    int vaule[N];
    for(int i=1;i<=N;i++)
      cin>>weight[i]>>vaule[i];
    int f[V+1];
        for(int j=0;j<=V;j++)
        {
            f[j]=0;
        }
    
    for(int i=1;i<=N;i++)
    {
    /*******************************************************************************************
     ///主要的不同就在这里,就是01背包的起始范围反转一下,为什么这样做呢?原因是由于范围是从weight[i]~V,从小到
    ///到大运行,这样f[j-weight[i](因为在j之前)在本次循环里就算过了,f[j-weight[i]]这个状态的意义是总数为j-weight[i]
    时,可能的最值,这里可能已经包含了若干个i物品,因为发f[j-weight]这个状态,是在第i次循环里面得到的。从而实现了
    一个i可以被取多次的条件。
    *************************************************************************************************/
        for(int j=weight[i];j<=V;j++) 
        {
            f[j]=max(f[j],f[j-weight[i]]+vaule[i]); 改成一维数组
        }
        
    }
    cout<<f[V];
}
复杂度较高的直观写法
 #include<iostream>
using namespace std;


int main()
{
    int N,V;
    cin>>N>>V;
    int weight[N];
    int vaule[N];
    for(int i=1;i<=N;i++)
        cin>>weight[i]>>vaule[i];
    int dp[V+1];
    
        for(int j=0;j<=V;j++)
            dp[j]=0;
    
    
    for(int i=1;i<=N;i++)
    {
        for(int j=V;j>=0;j--)  ///从后往前便利是为了变一维数组时,用到的是i-1时刻的状态
        {
            for(int k=0;k<=j/weight[i];k++)
            {
                dp[j] = max(dp[j], dp[j-weight[i]*k] + k*vaule[i]);
            }
        }
    }
    cout<< dp[V]<<endl;
    return 0;
}

  • 多重背包问题
    多重背包是限制单个物品个数的完全背包问题,其实就是在01背包上在套一个循环用来限制单个物体的取样次数。
#include<iostream>
using namespace std;
int main()
{
    int N,V;
    cin>>N>>V;
    int weight[N];int value[N];int count[N];
    for(int i=0;i<N;i++)
        cin>>weight[i]>>value[i]>>count[i];
    int dp[V+1];
    for(int i=0;i<V+1;i++)
        dp[i]=0;
    for(int i=0;i<N;i++)
    {
        for(int j=V;j>=0;j--)
        {
            for(int k=0;(k<=count[i])&&(k*weight[i]<=j);k++)
               dp[j]=max(dp[j],dp[j-k*weight[i]]+k*value[i]);  这里比01背包多加了一层循环,用来约束单个物体的取样次数问题
        }
    }
    cout<<dp[V];
    return 0;
}

优化解法(二进制优化)
多重背包的解法出现了三层循环,可以优化成两层循环,具体的优化思想是,每个物体可以取s次,可以把这s次当成一样的s个独立的物体,这样,多重背包问题就转化成了01背包问题,但是这里面有一个问题,就是将每个物体很多次这个事儿看成独立的物体,s的总次数如果比较多,就会出现较高的复杂度,于是使用取log的方式优化,举个例子,物体a可以选10次
那么现在把10拆分:
10=1+2+4 10-(1+2+4)=3 那么现在将10分成1,2,4,3这四组,我们可以发现,这四组,0~10都可以由这4个数通过某种方式组合得到,所以10次现在就等价于1,2,4,3次,这样就把原本需要拆分10次的问题变成了拆分4次,降低了时间消耗。

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    struct Good
    {
        int w,v;
    };
    vector<Good> Goods;
    int N,V;
    cin>>N>>V;
    int dp[V+1];
    for(int i=0;i<V+1;i++)
      dp[i]=0;
    for(int i=0;i<N;i++)
    {
        int w,v,s;
        cin>>w>>v>>s;
        for(int k=1;k<=s;k*=2)
        {
            s-=k;
            Goods.push_back({w*k,v*k});
        }
        if(s>0)
            Goods.push_back({w*s,v*s});
    }
    for(auto i:Goods)
    {
        for(int j=V;j>=i.w;j--)
        {
            dp[j]=max(dp[j],dp[j-i.w]+i.v);
        }
        
    }
    cout<<dp[V];
    return 0;

}

优化解法2(单调队列解法) ///先放一放,去看看滑动窗口。。。

- 混合背包问题
第一类物品只能用1次
第二类物品可以用无限次
第三类物品可以用多次
混合背包问题其实就是01背包、完全背包、多重背包的组合题,只需要根据条件带入不同的情况就行了


时间复杂度较高的写法(主要是因为多重背包问题造成的,可以使用二进制优化)

#include<iostream>
using namespace std;
int main()
{
    int N,V;
    cin>>N>>V;
    int dp[V+1];
    /*struct Thing
    {
        int kind;
        int w;
        int V;
    };*/
    for(int i=0;i<V+1;i++)
        dp[i]=0;
    for(int i=0;i<N;i++)
    {
        int w,v,s;
        cin>>w>>v>>s;
        if(s==-1)
        {
            for(int j=V;j>=w;j--)
                dp[j]=max(dp[j],dp[j-w]+v);
        } 
        else if(s==0)
        {
            for(int j=w;j<=V;j++)
            {
                dp[j]=max(dp[j],dp[j-w]+v);
            }
        }
        else
            for(int j=V;j>=0;j--)
            {
                for(int k=0;k*w<=j&&k<=s;k++)
                    dp[j]=max(dp[j],dp[j-k*w]+k*v);
            }
    }
     cout<<dp[V];
        return 0;
}

二进制优化(把所有的数据放一个结构体里)

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    int N,V;
    cin>>N>>V;
    int dp[V+1];
    struct Thing
    {
        int w;
        int v;
        int kind;
    };
    vector<Thing>Things;
    for(int i=0;i<V+1;i++)
        dp[i]=0;
    for(int i=0;i<N;i++)
    {
        int w,v,s;
        cin>>w>>v>>s;
        if(s==-1)
        {
            Things.push_back({w,v,s});
        }
        else if(s==0)
        {
            Things.push_back({w,v,s});
        }
        else
        {
            for(int k=1;k<=s;k*=2)
            {
                s-=k;
                Things.push_back({k*w,k*v,-1});
            }
            if(s>0)
                Things.push_back({s*w,s*v,-1});
        }
    }
    for(auto i:Things)
    {
        if(i.kind==-1)
        {
            for(int j=V;j>=i.w;j--)
                dp[j]=max(dp[j],dp[j-i.w]+i.v);
        }
        else
        {
            for(int j=i.w;j<=V;j++)
            {
                dp[j]=max(dp[j],dp[j-i.w]+i.v);
            }
        }
    }
     cout<<dp[V];
        return 0;
}
  • 二维背包问题
    一个物体不仅有体积,还有重量
    用一个二维数组保存状态dp[j][k]表示j体积,k重量下的最大值
#include<iostream>
using namespace std;
int main()
{
    int N,V,M;
    cin>>N>>V>>M;
    int dp[V+1][M+1];
    for(int i=0;i<V+1;i++)
    {
        for(int j=0;j<M+1;j++)
        dp[i][j]=0;
    }
    for(int i=0;i<N;i++)
    {
        int w,m,v; ///体积、重量、价值
        cin>>w>>m>>v;
        for(int j=V;j>=w;j--)
        {
            for(int k=M;k>=m;k--)
                dp[j][k]=max(dp[j][k],dp[j-w][k-m]+v);
        }
    }
    cout<<dp[V][M];
    return 0;

}
  • 分组背包问题

分组背包问题是把物体分成若干组,每一组里只能选一个物体
比如当前循环到了第i组,需要从第i组中挑出一个物体
for(int a=0;a<第i组的物体个数;a++)
{
for(j=V;j>a这个物体的体积;j++) 这里能体现出同一组物体之间的互斥关系
dp[j]=max{dp[j],dp[j-a的体积]+a的价值
}

#include<iostream>
using namespace std;
int main()
{
    int N,V;
    cin>>N>>V;
    int dp[V+1];
    for(int i=0;i<V+1;i++)
        dp[i]=0;
    int weight[101]{0};
    int value[101]{0};
    for(int i=0;i<N;i++)
    {
        int a; ///某组有几个物品
        cin>>a;
        for(int j=0;j<a;j++)
        {
           cin>>weight[j]>>value[j];
        }
        for(int k=V;k>=0;k--)
        {
            for(int r=0;r<a;r++)
            {
                if(weight[r]<=k)
                 dp[k]=max(dp[k],dp[k-weight[r]]+value[r]);
            }
               
        }
    }
    cout<<dp[V];
    return 0;
}
  • 背包问题求方案数
    最优选择一共有多少种不同的方案
#include<iostream>
using namespace std;
int main()
{
    int mod=100000007;
    int N,V;
    cin>>N>>V;
    int f[V+1];  
 /*****************************************************************************
注意这里与01背包的区别,01背包的f数组表示体积小于等于j时的最大价值,但是j并不一定全用了,比如体积是10的时候最大价值是5,但是可能在体积是9的时候最大价值就已经是5了,体积是10的时候的最大价值
其实和体积为9的时候是一样的。那么,如何让数组f[j]表示的是正好体积是j的时候的方案数呢?在初始化f[j]
数组的时候,如果f[0]等于0,其他的都初始化为负无穷,这样所有的状态都是从f[0]转换过来的,这样就可以
了。
如果像01背包那样,需要把f[0]数组全部初始化为0
************************************************************************************/
    int g[V+1]; ///开一个g数组,表示最大价值正好是j时,总的方案数是多少
    for(int i=0;i<V+1;i++)
    {
        f[i]=INT32_MIN;
        g[i]=0;
    }
    g[0]=1;
    for(int i=0;i<N;i++)
    {
        int w,v;
        cin>>w>>v;
        for(int j=V;j>=w;j--)
        {
            int t=max(f[j],f[j-w]+v);
            int s=0;
            if(t==f[j]) s=s+g[j];
            if(t==f[j-w]+v) s=s+g[j-w]; 看t是从哪个状态转移过来的
            if(s>=mod) s-=mod;
            f[j]=t;
            g[j]=s;
        }
    }
    int maxv=0;
    for(int i=0;i<=V;i++)
    {
        maxv=max(maxv,f[i]);  ///找出最优的方案
    }
    int res=0;
    for(int i=0;i<=V;i++)
    {
        if(f[i]==maxv)
        {
            res+=g[i];     统计最优方案的方案种数
            if(res>=mod)res-=mod;
        }
    }
    cout<<res;
    return 0;
}
  • 求背包问题的一个具体方案(输出字典排序最小的一个)
#include <iostream>
using namespace std;
int main() {
    int N, V;
    cin >> N >> V;
    int w[N + 1];
    w[0] = 0;
    int v[N + 1];
    v[0] = 0;
    int dp[N + 2][V + 2];
    for (int i = 0; i < N + 2; i++) {
        for (int j = 0; j < V + 2; j++)
            dp[i][j] = 0;
    }
    for (int i = 1; i < N + 1; i++) {
        cin >> w[i] >> v[i];
    }
从第N个物品开始取,直到取到第一个物体,为什么这么取呢,因为我们想要字典排序最小的输出,所以比
如我们需要先确定第一个物体要不要选,如果要选,把他输出出来,而不是先找第N个物体要不要选。
    for (int i = N; i >=1; i--) {
        for (int j = 0; j <= V; j++) {
            dp[i][j] = dp[i+1][j];
            if (j >= w[i])
                dp[i][j] = max(dp[i][j], dp[i+1][j - w[i]] + v[i]);
        }
    }
    int vol=V;
    for(int i=1;i<=N;i++) 
    {
        if(vol>=w[i]&&dp[i][vol]==dp[i+1][vol-w[i]]+v[i])
        {
            cout<<i<<" ";
            vol-=w[i];
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
买书问题 dp实现 题目:买书 有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下: 如果单独购买其中一卷,那么可以打9.5折。 如果同时购买两卷不同的,那么可以打9折。 如果同时购买三卷不同的,那么可以打8.5折。 如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。 1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案), 或者三本一起买(这有一种方案),最后直到买完所有需要的书。 2、最后一步我必然会在7种购买方案中选择一种,因此我要在7种购买方案中选择一个最佳情况。 3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数 。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有"子问题重叠",问题中三个购买量设置为参数, 分别为i、j、k。 4、的确符合。 5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。 6、每次选择最多有7种方案,并且不会同时实施其中多种,因此方案的选择互不影响,所以有"子问题独立"。 7、我可以用minMoney[i][j][k]来保存购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱。 8、共有x * y * z个问题,每个问题面对7种选择,时间为:O( x * y * z * 7) = O( x * y* z )。 9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱,那么有: MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱: s1 = 60 * 0.95 + MinMoney(i-1,j,k) s2 = 60 * 0.95 + MinMoney(i,j-1,k) s3 = 60 * 0.95 + MinMoney(i,j,k-1) s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k) s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1) s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值