动态规划:背包问题

 01背包问题:

每件物品只能用一次

二维做法:

#include

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 

int main() 
{
    int n, m; //物品数量、背包容积  
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++) 
        scanf("%d%d",&v[i],&w[i]);//给每个物品赋大小和价值

    for(int i = 1; i <= n; i++) //i是当前物品,一共n个物品
        for(int j = 0; j <= m; j++)//j为当前剩余容积,一定要小于一共的容积,当前剩余容积从0开始,每次+1,直到有m容积
        {
            //当前容量装不进第i个物品,则当前容积的价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行判断是否选择第i个物品
            else    //判断方法:不要i物品或要i物品,但是要i的话就看看容量-i的体积,剩下的容量装了多少价值。再比较是否要i
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }  
        
    printf("%d\n",f[n][m]);

    return 0;
}

 一维做法:

#include

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值 
int f[MAXN];  // f[j],背包容量j下的最优解。

int main() 
{
    int n, m; //物品数量、背包容积  
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++) 
        scanf("%d%d",&v[i],&w[i]);//给每个物品赋大小和价值

    for(int i = 1; i <= n; i++) //i是当前物品,一共n个物品
        for(int j = m; j >=v[i]; j--)//j为当前剩余容积,一定要大于要操作的物品的大小,当前剩余容积从m开始,每次-1
        {
            //当前容量装不进第i个物品,则当前容积的价值等于前i-1个物品,则不会进入循环
            
            // 能装,需进行判断是否选择第i个物品
            //判断方法:遍历到前i-1个物品的j的容量保存的价值 比较 遍历到前i-1个物品的j-v[i]的容量加i的价值
            //不要i物品或要i物品,但是要i的话就看看容量-i的体积,剩下的容量装了多少价值。再比较是否要i
            
            //为什么一维情况下枚举背包容量需要逆序?
            //在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。
            //而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
            
            //例如,一维状态第i轮对体积为 3 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。
            //当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,从f[7]更新时用的到f[4]还未更新,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }  
        
    printf("%d\n",f[m]);

    return 0;
}

完全背包问题:

每件物品可以用无限次

 二维做法:

#include
using namespace std;
const int N = 1010;
int f[N][N];// f[i][j], j体积下前i个物品的最大价值 
int v[N];// 体积
int w[N];// 价值 
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);//物品数量、背包容积
    for(int i = 1 ; i <= n ;i ++)//i是当前物品,一共n个物品
    {
        scanf("%d%d",&v[i],&w[i]);//给每个物品赋大小和价值
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = 0 ; j<=m ;j++)//j为当前剩余容积,一定要小于一共的容积,当前剩余容积从0开始,每次+1
    {
        //当前容量装不进第i个物品,则当前容积的价值等于前i-1个物品
        if(j < v[i]) 
            f[i][j] = f[i - 1][j];
        else//是否选择该物品   
        //不要i物品或要i物品,但是要i的话就看看容量-i的体积,剩下的容量装了多少价值,再比较是否要i
        //不用i则用i-1的状态,用i则用i的状态,因为i可以无限使用
            f[i][j] = max(f[i - 1][j],f[i][j - v[i]] + w[i]);
    }

    printf("%d\n",f[n][m]);
    return 0;
}

一维做法:

#include
using namespace std;
const int N = 1010;
int f[N];// f[j], j体积下前i个物品的最大价值 
int v[N];// 体积
int w[N];// 价值 

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);//物品数量、背包容积
    for(int i = 1 ; i <= n ;i ++)//i是当前物品,一共n个物品
    {
        scanf("%d%d",&v[i],&w[i]);//给每个物品赋大小和价值
    }

    for(int i = 1 ; i<=n ;i++)
    //这里用v[i]的体积作为初始体积,也就是说不看i前面的物品了,只看i后面的物品。但i是从第一个开始到第n个的,所以不会有问题
    for(int j = v[i] ; j<=m ;j++)//f为i物品的体积注意了,这里的j是从小到大枚举,和01背包不一样
    {
        //当前容量装不进第i个物品,则当前容积的价值等于前i-1个物品,则不会进入循环
            
        // 能装,需进行判断是否选择第i个物品
        //遍历到前i个物品的j的容量保存的价值 比较 遍历到前i-1个物品的j-k*v[i]的容量加k*i的价值
        //注意是遍历到i-1个物品,因为可能装不进,所以j-k*v[i]的容量不一定是i-1的价值
        //若i-1的物品的大小大于i物品,且价值小于i,所以后者更小
        //若i-1的物品的大小小于i物品,且价值大于i,所以后者更大
        f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

    printf("%d\n",f[m]);
    return 0;
}

多重背包问题 I:

每个物品有不同的次数限制 

二维做法:

#include 
#include 

using namespace std;
const int N = 110;

int v[N];// 体积
int w[N];// 价值 
int s[N];// 该物品的次数
int f[N][N];// f[i][j], j体积下前i个物品的最大价值 
int n, m;

int main()
{
    scanf("%d%d",&n,&m);//物品数量、背包容积
    for(int i = 1; i <= n; i ++) //i是当前物品,一共n个物品
        scanf("%d%d%d",&v[i],&w[i],&s[i]);//给每个物品赋大小和价值和次数

    for(int i = 1; i <= n; i ++)
    {
        for(int j = 0; j <= m; j ++)//j为当前剩余容积,一定要小于一共的容积,当前剩余容积从0开始,每次+1
        {
            for(int k = 0; k <= s[i]&&k*v[i]<=j; k ++)//该物品放入k次,直到容量不够放或达到次数限制
            {
                //遍历到前i个物品的j的容量保存的价值 比较 遍历到前i-1个物品的j-k*v[i]的容量加k*i的价值
                //注意是遍历到i-1个物品,因为可能装不进,所以j-k*v[i]的容量不一定是i-1的价值
                //若i-1的物品的大小大于i物品,且价值小于i,所以后者更小
                //若i-1的物品的大小小于i物品,且价值大于i,所以后者更大
                //比较当前的价值和上一物品的价值加k次当前物品的价值
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    }
    printf("%d\n",f[n][m]);

    return 0;
}

一维做法:

#include
using namespace std;

const int N = 12010, M = 2010;

int n, m;
int v[N];// 体积
int w[N];// 价值 
int f[M]; // f[j], j体积下前i个物品的最大价值 

int main()
{
    scanf("%d%d",&n,&m);//物品数量、背包容积
    int cnt = 0; //分组的组别
    for(int i = 1;i <= n;i ++)
    {
        int a,b,s;
        scanf("%d%d%d",&a,&b,&s);//第 i 种物品的体积、价值和数量。
        int k = 1; // 组别里面的个数
        while(k<=s)//组别里的个数要小于等于它有的数量
        {
            cnt ++ ; //组别先增加:1 2 4 8 16 32 64 128...
            v[cnt] = a * k ; //整体体积:该物品的体积*个数
            w[cnt] = b * k; // 整体价值:该物品的价值*个数
            s -= k; // s要减小,使用了k个,还剩多少,这个是分组的内容:7可以分成1+2+4,所以这里s=7,6,4,0
            k *= 2; // 组别里的个数增加,因为是2指数增长k:1 2 4 8(没有到8就直接跳出),k=4时 s=4.这次后直接跳出循环
        }
        //剩余的一组
        if(s>0)//此时若s还未到0,则说明还有一个数c(第一张图中的c)
        {
            cnt ++ ;
            v[cnt] = a*s;//该物品的体积*该物品剩余的数量
            w[cnt] = b*s;//该物品的价值*该物品剩余的数量
        }
    }

    n = cnt ; //枚举次数正式由个数变成组别数,一共就只用访问n次就行了,比如7就只用访问3次

    //01背包一维优化
    for(int i = 1; i <= n; i++) //i是当前物品,一共n个物品
        for(int j = m; j >=v[i]; j--)//j为当前剩余容积,一定要大于要操作的物品的大小,当前剩余容积从m开始,每次-1
        {
            //当前容量装不进第i个物品,则当前容积的价值等于前i-1个物品,则不会进入循环
            
            // 能装,需进行判断是否选择第i个物品
            //判断方法:遍历到前i-1个物品的j的容量保存的价值 比较 遍历到前i-1个物品的j-v[i]的容量加i的价值
            //注意是遍历到i-1个物品,因为可能装不进,所以j-v[i]的容量不一定是i-1的价值
            
            //为什么一维情况下枚举背包容量需要逆序?
            //在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。
            //而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
            
            //例如,一维状态第i轮对体积为 3 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。
            //当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,从f[7]更新时用的到f[4]还未更新,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。
            
            //此时的w存的就是组别的价值,而非某个物品的价值
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        } 

    printf("%d\n",f[m]);
    return 0;
}

分组背包问题:

每组有不同的体积和价值的物品,每组只能选一个物品

#include
using namespace std;

const int N=110;
int f[N];// f[j], j体积下前i个物品的最大价值 
int v[N][N];  // 体积
int w[N][N]; // 价值 
int s[N];//i组内的物品
int n,m,k;

int main()
{
    scanf("%d%d",&n,&m);//物品数量、背包容积  
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&s[i]);//第i组
        for(int j=1;j<=s[i];j++)
        {
            scanf("%d%d",&v[i][j],&w[i][j]);//i组内的j物品的体积和价值
        }
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=0;j--)//让体积一开始就是m,每次都让体积减小1
        {
            for(int k=1;k<=s[i];k++)//遍历组内物品
            {
                if(j>=v[i][k])//若剩余体积大于该物品体积,判断是否加入ik物品
                    //不要ik物品或要ik物品,但是要ik的话就看看容量-ik的体积,剩下的容量装了多少价值。再比较是否要ik
                    f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);  
            }
        }
    }
    printf("%d\n",f[m]);
}

你可能感兴趣的:(算法基础,动态规划,算法)