dp动态规划 背包问题

学习视频1
学习视频2(灯神)

01背包(每个物品就1个)

理解:
image

动态转移方程:
dp[i][j]=max(dp[i-1][j] , dp[i-1][ j-p[i] ]+m[i]);
代码:

#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;

int main()
{
    int n,x;
    while(~scanf("%d %d",&n,&x))
    {
        int p[n+1],m[n+1];
        int i,j;
        int dp[n+1][x+1]={{0}};

        for(i=1;i<=n;i++)
        {
            cin>>p[i]>>m[i];

        }
         for(i=0;i<=x;i++)
        {
            dp[0][i]=0;
        }

        for(i=1;i<=n;i++)
        {
            for(j=1;j<=x;j++)//j表示当前背包容量
            {
                if(p[i]> j)//如果该物品容量大于当前背包容量
                {
                    dp[i][j]=dp[i-1][j];
                }
                else
                {
    dp[i][j]=max(dp[i-1][j] , dp[i-1][ j-p[i] ]+m[i]);
                }

            }
        }
        cout<<dp[n][x]<<endl;
    }
    return 0;
}

改进

因为每层都只于上层有关,故压缩数组,
又因为从小到大会覆盖到上层数据,故从小到大更新数据
优化后:

#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;

int main()
{
    int n,x;
    while(~scanf("%d %d",&n,&x))
    {
        int p[n+1],m[n+1];
        int i,j;
        int dp[x+1]= {0};

        for(i=1; i<=n; i++)
        {
            cin>>p[i]>>m[i];

        }
        for(i=0; i<=x; i++)
        {
            dp[i]=0;
        }

        for(i=1; i<=n; i++)
        {
            for(j=x; j>=1; j--) //j表示当前背包容量
            {
                if(j>=p[i])
                {
                    dp[j]=max(dp[j], dp[j-p[i] ]+m[i]);
                }

            }
        }

        cout<<dp[x]<<endl;
    }
    return 0;
}

完全背包问题(物品个数无限)

朴素算法

只比01背包多了一层更新放置几个物品的循环

#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;

int main()
{
    int n,x;
    while(~scanf("%d %d",&n,&x))
    {
        int p[n+1],m[n+1];
        int i,j;
        int dp[x+1]= {0};

        for(i=1; i<=n; i++)
        {
            cin>>p[i]>>m[i];

        }
        for(i=0; i<=x; i++)
        {
            dp[i]=0;
        }

        for(i=1; i<=n; i++)
        {
            for(j=x; j>=1; j--) //j表示当前背包容量
            {
	  	//*****************只多这些
                for(int k=0;k<=j/p[i];k++)
	      //只多这些*****************
                    dp[j]=max(dp[j],dp[ j- k*p[i] ]+k*m[i]);


            }
        }

        cout<<dp[x]<<endl;
    }
    return 0;
}


改进中间态

image
动态转移方程:
dp[i][j]=max(dp[i-1][j] , dp[ i ] [ j-p[i] ]+m[i]);
与01区别:后面为dp[i]行

改进

由改进中间态得:只需要把01改进算法,改为从小到大推即可

#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;

int main()
{
    int n,x;
    while(~scanf("%d %d",&n,&x))
    {
        int p[n+1],m[n+1];
        int i,j;
        int dp[x+1]= {0};

        for(i=1; i<=n; i++)
        {
            cin>>p[i]>>m[i];

        }
        for(i=0; i<=x; i++)
        {
            dp[i]=0;
        }

        for(i=1; i<=n; i++)
        {
            for(j=p[i]; j<=m; j++)//从可以放入一个p[i]开始处理
                //j表示当前背包容量
            {
           dp[j]=max(dp[j],dp[ j- p[i] ]+m[i]);
            }
        }

        cout<<dp[x]<<endl;
    }
    return 0;
}


多重背包(物品个数有限)

两个条件:
1.和完全背包一样
2.数量个数有限

朴素算法

代码思想:从01背包的状态转移方程式,去增加第i个物品拿k个的循环

#include <iostream>
#include<cstring>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;

int main()
{
    int p[501];//价格
    int v[501];//价值
    int s[501];//数量
    int dp[6100];
    int n,m;
    cin>>n>>m;
    int i,j,k;
    for(i=1;i<=n;i++)
    {
        cin>>p[i]>>v[i]>>s[i];
    }
    for(i=1;i<=n;i++)
    {
        for(j=m;j>=1;j--)
        {
            for(k=0;k<=s[i]&&j>=k*p[i];k++)
            {
//从01背包的状态转移方程式,去增加第i个物品拿k个的循环
        dp[j]=
           max(dp[j],dp[j-k*p[i]] +k*v[i] );
            }
        }
    }
    cout<<dp[n];
    return 0;
}

改进:利用二进制

没学过二进制, 就不写了
二进制思想:

假设有 1000 个苹果,现在要取n个苹果,如何取?朴素的做法应该是将苹果一个一个拿出来,直到n个苹果被取出来。
再假设有 1000 个苹果和10只箱子,利用箱子进行某些预工作,然后如何快速的取出n个苹果呢?So..可以在每个箱子中放 2^i (i<=0<=n)个苹果,也就是 1、2、4、8、16、32、64、128、256、489(n减完之前的数之后不足 2^i,取最后剩余的数),相当于把十进制的数用二进制来表示,取任意n个苹果时,只要推出几只箱子就可以了。
基于这种思想把一种多件物品转换为,多件一种物品,然后用01背包求解即可。

二维费用背包

解法:多加一维就可

const int maxn=1e4+5;
int n,x,y,w[N+1],v[N+1],g[N+1];
int dp[X+1][Y+1];

int main()
{
    cin>>n>>x>>y;
    for(int i=1;i<=n;i++)
        cin>>w[i]>>v[i]>>g[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=x;j>=w[i];j--)
        {
            for(int k=y;k>=v[i];k--)
            {
                dp[j][k]=max(dp[j][k],dp[j-w[i]][k-v[i]]+g[i]);
            }
        }
    }
    cout<<dp[x][y]<<endl;
    return 0;
}

实战1.sdut- 最少硬币问题

#include <iostream>
#include <cstdio>
#include <cstring>///包含memset()
#define MAX 0x3f3f3f3f ///定义最大值MAX
using namespace std;

int min(int, int);
int T[15];
//T[i]表示 第i种硬币的面值
int coins[15];
//coins[i]表示第i种硬币的个数
long long int dp[20011];
//dp[目标面额] 表示达到当前面额需要最少的硬币数量
int main()
{
    int n, m;
  
    scanf("%d", &n);
    for(int i = 0; i < n; i++)   
               cin>>T[i]>>coins[i];
    scanf("%d", &m);
    
    memset(dp, MAX, sizeof(dp));//初始化为最大值
    dp[0] = 0;//边界条件,当目标面额是0时,需要0个硬币

    for(int i = 0; i < n; i++)
    {//i是第i种硬币,遍历每一种硬币
        for(int j = 1; j <= coins[i]; j++)
        {//j是硬币个数,每一种硬币有coins[i]个,对第i种硬币的每一个硬币都遍历
            for(int k = m; k >= T[i]; k--)
            {//k是当前面额,当需要找回的面额 大于等于 第i种硬币的面值
            //判断已保存的dp[当前面额]的值 和 dp[替换掉当前面额]的值 的大小
                dp[k] = min(dp[k], dp[k - T[i]] + 1);
            }
        }
    }
    printf("%lld\n", dp[m] < MAX  ? dp[m]  :-1);

    return 0;
}


posted @ 2021-08-17 10:54  kingwzun  阅读(100)  评论(0编辑  收藏  举报