史上最全背包问题类型总结!!!!

各类背包问题题型总结

混合背包问题

有 N 种物品和一个容量是 V的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);

解法:将各种背包利用二进制优化的思想存为一个个单独问题,再按01背包问题求解即可

	for (int i = 1; i <= n; i++)
	{
		int v, w, s; cin >> v >> w >> s;
		
		if (s == -1)
			s = 1;
			
		if (s == 0)
			s = m / v;
			
		int k = 1;
		
		while (k <= s)
		vv[cnt] = k * v;ww[cnt] = k * w;cnt++;s -= k;k*=2;
		
		if (s) 
			vv[cnt] = v * s;ww[cnt] = w * s;cnt++;
	}

二维花费的背包问题

有 N件物品和一个容量是 V的背包,背包能承受的最大重量是 M

每件物品只能用一次。体积是 vi重量是 mi价值是 wi

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。

解法,增加了一维的费用就增加一层for循环即可

参考代码:
for (int i = 1; i <= n; i++)
	{
		for (int j = v; j >= vv[i]; j--)
		{
			for (int k = m; k >= mm[i]; k--)
			{
				f[j][k] = max(f[j][k], f[j - vv[i]][k - mm[i]] + ww[i]);
			}
		}
	}

二位花费下的大于给定体积的最小价值

for(int i =1;i<=n;i++)
	{
	    for(int j= v1;j>=0;j--)
	    {
	        for(int k = v2;k>=0;k--)
	        {
	            f[j][k] = min(f[j][k],f[max(j-vv1[i],0)][max(k-vv2[i],0)]+w[i]);
	            
	        }
	    }
	}

求最优方案的个数(能得到最大价值的方案有几种)

for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= v[i]; j--)
		{
			if (f[j - v[i]] + w[i] > f[j])//如果发现选了当前物品后,j体积下的最大价值变大了,那么就更新最大价值
			//以及j体积最大价值对应的方案数;
			{

				f[j] = f[j - v[i]]+w[i];
				num[j] = num[j-v[i]];//num[j]指的是前i-1个物品中得到j价值的方案个数
			}
		else 	if (f[j - v[i]] + w[i] == f[j])
			{
				num[j] = (num[j - v[i]] + num[j]) % mod;//在这段代码更新num[j]之前,num[j]的所有方案都不选当前物品
				//而num[j-v[i]]的这些方案现在都选了当前物品,这些方案数的最大价值相同,所以合并到一块
			}
		}
	}
memcpy的使用(插入知识点)
函数原型

void *memcpy(void*dest, const void *src, size_t n);


功能

由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。

背包问题-----求字典序最小的最优方案,同时输出这个方案

{求动态规划的路径,需要从后往前推,判断当前状态可以从哪个状态来到达}

f[i][j]表示从第i个物品到最后一个物品,体积不大于j的最大价值
for (int i = n; i>=1; i--)
	{
		for (int j = 1; j <= sum; j++)
		{
			f[i][j] = f[i + 1][j];
			if (j >= v[i])
			{
				f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
			}
		}
	}
	
	'输出路径':从前往后遍历来保证最小字典序,如果当前物品在最优方案里面,
	//那么 f[i][j] == f[i + 1][j - v[i]] +w[i]
		int j = sum;
	for (int i = 1; i <= n; i++)
	{
		if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
		{
			cout << i << " ";
			j -= v[i];
		}
	}

混合背包问题下的方案数问题**

思路就是把01背包和完全背包分开求方案数

f[0] = 1; // 一枚硬币都没有的时候,只有一种就是都不选
    for (int i = 1; i <= n1; i ++ ) { // 普通币:完全背包
        int p; // 第i种纪念币的面值是p
        cin >> p;
        for (int j = p; j <= m; j ++ ) f[j] = (f[j] + f[j - p]) % MOD;
    }
    for (int i = 1; i <= n2; i ++ ) { // 纪念币:0/1背包
        int p;
        cin >>  p;
        for (int j = m; j >= p; j -- ) f[j] = (f[j] + f[j - p]) % MOD;
    }
    cout << f[m] << endl;


分组背包问题下的求最优方案,并输出这个方案(难点)(acwing1013机器分配)

重点在于如何输出这个路径
输出路径的思路:

  1. 用一个数组g[i][j]来记录从第一组物品到当前i组物品为止,在总体积为j的情况下,选择i组物品中的体积为多少的物品(如果没选就是0)
  2. 然后就是print函数,第一参数代表当前遍历到了第几组,第二个参数是从第一组到当前组一共有多少体积可以分配(注意每行代码的顺序)
  3. 注意调用print函数时,一开始传的参数是最后一组以及最大体积(相当于逆序遍历)
int f[N], w[N], g[N][N];



void print(int x, int y)
{
    if(x==0) return ;
    int k  = g[x][y];
    print(x-1,y-k);
    printf("%d %d\n",x,k);
    
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)  scanf("%d", &w[j]);

       for(int j = m;j>=1;j--)
       {
           for(int k = 1;k<=j;k++)
           {
              if(f[j-k]+w[k]>f[j])
              {
                  f[j] = f[j-k]+w[k];
                  g[i][j] = k;
              }
           }
       }
    }

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

做题经验总结

如果某种物品可以无限选,那么for循环正序进行,如果只能选一个,就逆序
如果是大于给定体积下的最小值,for循环需要到0(可参考今日昨日的博客)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值