关于背包的几种常见题型

8 篇文章 0 订阅
6 篇文章 0 订阅

背包

背包是dp(动态规划)的一类常见题型,题目变幻莫测,就像:
某牛客网and某杭大and还是杭大and某落谷
算了,我都受不了。
那么,我们就开始细细分析一下背包问题们;↓

01背包

关于01背包,本意是给定N件重量为m[i]、价值为w[i]的物品和承重为V的背包,求在背包承重范围内接受物品的最大价值。
很明显,01背包的特点就是 每件物品只有一件,选择放/1或者不放/0。

那么最重要的是,转移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-m[i]]+w[i]);

没错,一点都不难理解,对于dp[i-1][j],即为不去物品i,后者当然是取物品i并且价值加上w[i]啦
easy

所以?这不重要,关键是我们还可以将状态压缩成一维形态,由于i只回溯到i-1的纵向级别,所以说:

for(int i=1->n)
for(int j=v->1)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

对,我们将i放到for循环中去了,这样我们就节约了极大的空间来支撑其余的储存(一些杂题中,此处不作探讨)。同时因为需要追溯dp[j-v[i]],故而我们让体积由后向前循环。

来一段82年的code压压惊:见题

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
 
int dp[3000100];//每张发票不超过1000,最多30张,扩大100倍数后就要开这么大
 
int main()
{
    char ch;
    double x,y;
    int sum,a,b,c,money[35],v;
    int t,i,j,k;
    while (~scanf("%lf%d",&x,&t),t)
    {
        sum = (int)(x*100);//将小数化作整数处理
        memset(money,0,sizeof money);
        memset(dp,0,sizeof dp);
        int l = 0;
        for (i = 0; i<t; i++)
        {
            scanf("%d",&k);
            a = b = c = 0;
            int flag = 1;
            while (k--)
            {
                scanf(" %c:%lf",&ch,&y);
                v = (int)(y*100);
                if (ch == 'A' and a+v <= 60000)
                    a += v;
                else if (ch == 'B' and b+v <= 60000)
                    b += v;
                else if (ch == 'C' and c+v <= 60000)
                    c += v;
                else
                    flag = 0;
            }
            if (a+b+c <= 100000 and a <= 60000 and b <= 60000 and c <= 60000 and flag)//已知必须满足这些条件
                money[l++] = a+b+c;
        }
        for (i = 0; i <= l; i++)
        {
            for (j = sum; j >= money[i]; j--)
                    dp[j] = max(dp[j],dp[j-money[i]]+money[i]);
        }
        printf("%.2lf\n",dp[sum]/100.0);
    }
 
    return 0;
}

Ps:这是很浅显的一种变式

完全背包

本意~~(基本大意)~~ 就是给定N重量为m[i]、价值为w[i]的物品和承重为V的背包,求在背包承重范围内接受物品的最大价值。
很明显,这里多出了一个信息,那就是每种物品可以有若干件,也就是说,我们甚至可以无限制地去取一样单位空间最贵的物品,但是难免会碰到如下情况:

2 5 //物品种数及背包空间
5 20 //分别代表物品体积及价值,下同
2 9

是个明眼人都看出来了,上述题目使用贪心必然会失败,所以动规是必不可少的。

重头戏登场:
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);

借用上面的思想,我们同样选择状压,因为本次使用的转移方程也是只涉及到i-1的纵向级别。但与上面不同的是,此次考虑的对象是dp[i][j-v[i]],所以我们必须让体积由前向后循环,正如:

for(int i=1->n)
for(int j=1->v)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

好的,堪称完美。

code以及某落谷上的noip

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

int dp[25005];
int f[25005];
int n,t,ans;

int main (){
    scanf("%d",&t);
    for (int i = 1 ; i <= t ; ++i){
        scanf("%d",&n);
    	memset(dp,0,sizeof dp);
        for (int i = 1 ; i <= n ; ++i){
            scanf("%d",&f[i]);
        }
        sort (f + 1 , f + n + 1);
        dp[0] = 1 ;//一个也不选方案数为1
        for (int i = 1 ; i <= n ; ++i){
            if (!dp[f[i]])
                ++ans ;//如果没有一个方案可以表示这个数,就表示这个数必须被选
            for (int j = f[i] ; j <= f[n] ; ++j)
                dp[j] += dp[j - f[i]];
        }
        printf ("%d\n",ans);
    }
    return 0 ;
}

Ps:实话说博主在参赛时也没有想到它是完全背包,第一反应就是小凯的诱惑,最后暴力也只打了20分惭愧啊
话说回来,这个变式是真的严重简单。

多重背包

为什么将完全放在第二个讲呢?
其实就是因为多重它比完全又多了一重限制规定(稍后看官们就知道真正的限制了),那就是这次的N种物品都是有指定数量n[i]的,这是一个非常令人伤心的规定,因为完全背包基本会爆,而转化为01又过于繁琐,那么我们这个时候就应该将01和完全思想结合。

来一道令旁人侧目的传送门

code:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int main()
{
    int T,n,m;
    int v[105],w[105],num[105],dp[105];
    cin>>T;
    while(T--)
    {
        cin>>m>>n;
        for(int i=0;i<n;i++)
            cin>>v[i]>>w[i]>>num[i];
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++)
            for(int j=0;j<num[i];j++)
                for(int k=m;k>=v[i];k--)
                    dp[k]=max(dp[k],dp[k-v[i]]+w[i]);
        cout<<dp[m]<<endl;
    }
    return 0;
}

Ps:We are comming.
Now next;↓

双重背包

没听过吧?长见识了吧?孤陋寡闻了吧?
这是一种很诡异的题型,因为他平白无故又多加了一个限制↓
这N件物品不仅有体积,竟然还有重量(虽然这很正常)m[i],在保证背包的V不被撑爆的同时,也要预防你被超过M的物品压shi,并且尽量地使价值总和W最大。

接下来是您的独秀时间……

好的,我知道你们不会)大家都是dalao,会是必须的啊

讲解时间

这种题目其实在我个人看来,并没有什么比较大的难度无非就是要开一个三维dp很明显,就是一个二维dp数组可以搞定的事情(时刻谨记状态压缩节省空间)。比如:

dp[i][j]=max(dp[i][j],dp[i-v[k]][j-m[k]]+w[k])
对于这个类型的话,给出转移方程就是一切了,循环之类的大家根据上面的方式也可以推出个大概,所以代码就算了吧
Ps:注意双重完全背包之类的组合提醒,这里就不在深究了。

但是博主就在准备收手的时候发现了一个惊为天人的情况 是我孤陋寡闻了 比如说:


简直是……

所以博主还要继续编辑树形dp,环型dp,数位dp和多维dp。

emmmm……谁能发现这一玄机呢?

Ps:所以重点是,我还是得先暂停是嘛)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值