背包问题(转载并且加上了自己的一些思路)


一,01背包问题

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

有N件物品和一个容量为V 的背包。放入第i件物品耗费的费用是Ci1,得到 的价值是Wi。求解将哪些物品装入背包可使价值总和最大。 也即占用背包的空间容量,后文统一称之为“费用(cost)”


用子问题定义状态:即F[i,v]表示前i件物品恰放入一个容量为v的背包可 以获得的最大价值。则其状态转移方程便是: F[i,v] = max{F[i−1,v],F[i−1,v−Ci] + Wi} //前i-1件背包的容量为v,放入第i件物品需要v减去第i件物品的的费用再加上第i件物品的价值

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生 出来的。


所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包 中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化 为一个只和前i−1件物品相关的问题。如果不放第i件物品,那么问题就转化 为“前i−1件物品放入容量为v的背包中”,价值为F[i−1,v];如果放第i件物 品,那么问题就转化为“前i−1件物品放入剩下的容量为v −Ci的背包中”, 此时能获得的最大价值就是F[i−1,v −Ci]再加上通过放入第i件物品获得的价 值Wi。

伪代码如下: F[0,0..V ] ← 0

for i ← 1 to N

for v ← Ci to V

F[i,v] ← max{F[i−1,v],F[i−1,v−Ci] + Wi}

1.3经过优化空间复杂度的伪代码

F[0..V ]←0

for i ← 1 to N

for v ← V to Ci//经过了逆序的思路

F[v] ← max{F[v],F[v−Ci] + Wi}

为什么这个算法就可行呢?首先想想为什么01背包中要按照v递减的次序来 循环。让v递减是为了保证第i次循环中的状态F[i,v]是由状态F[i−1,v −Ci]递 推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入 第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果F[i− 1,v −Ci]。

1.4 初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。 有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背 包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。 如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其 它F[1..V ]均设为−∞,这样就可以保证最终得到的F[V ]是一种恰好装满背包的 最优解。 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该 将F[0..V ]全部设为0。 这是为什么呢?可以这样理解:初始化的F数组事实上就是在没有任何物 品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量 为0的背包可以在什么也不装且价值为0的情况下被“恰好装满”,其它容量的 背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。如果背包并非 必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的 价值为0,所以初始时状态的值也就全部为0了。

例题

1033.采药

Time Limit: 1000 MS    Memory Limit: 32768 KB
Total Submission(s): 438    Accepted Submission(s): 127

Description

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。” 

如果你是辰辰,你能完成这个任务吗?

Input

输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间(1 <= t <= T)和这株草药的价值(1 <= v <= 100000)。

Output

输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

Sample Input

100 5
77 92
22 22
29 87
50 46
99 90

Sample Output

133




源代码如下,(已经AC过的)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int main()
{
    int T,M,v;
    int f[10005],c[10005],w[10005];
    while(scanf("%d %d",&T,&M)!=EOF)
    {
        memset(f,0,sizeof(f));
        memset(c,0,sizeof(c));
        memset(w,0,sizeof(w));
        for(int i=0; i<M; i++)
        {
            scanf("%d %d",&c[i],&w[i]);
        }
        for(int i=0; i<M; i++)
        {
            for(int j=T; j>=c[i]; j--)//背包的容量逐渐减少
            {
                if(f[j-c[i]]+w[i] > f[j])//放入第i件与不放第i件的总价值进行比较
                {
                    f[j] = f[j-c[i]] + w[i];
                }

            }
        }
        cout<<f[T]<<'\n';
    }
    return 0;
}


杭电上有很多关于01背包的问题,有意向的可以到杭电上去搜索练习


二,完全背包问题

完全背包和01背包的差别在于完全背包中每件物品都有无限件可用

2.2 基本思路
这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就 是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有 取0件、取1件、取2件……直至取⌊V /Ci⌋件等许多种。 如果仍然按照解01背包时的思路,令F[i,v]表示前i种物品恰放入一个容 量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方 程,像这样:
F[i,v] = max{F[i−1,v−kCi] + kWi |0 ≤ kCi ≤ v}

这跟01背包问题一样有O(V N)个状态需要求解,但求解每个状态的时 间已经不是常数了,求解状态F[i,v]的时间是O( v Ci ),总的复杂度可以认为 是O(NV Σ V Ci ),是比较大的。 将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明 01背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是 要试图改进这个复杂度。


2.5 O(V N)的算法
这个算法使用一维数组,先看伪代码:

F[0..V ]←0

for i ← 1 to N

for v ← Ci to V

F[v] ← max(F[v],F[v−Ci] + Wi)

你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而已。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加 选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结 果F[i,v−Ci],所以就可以并且必须采用v递增的顺序循环。这就是这个简单的 程序为何成立的道理。


例题

1043.采药2

Time Limit: 1000 MS    Memory Limit: 32768 KB
Total Submission(s): 236    Accepted Submission(s): 76

Description

XXX上山去采药。XXX有一个容量为m(1<=m<=1000)的背包,他所采集的药材的总重量不能大于背包的容量。已知共有n(1<=n<=1000 )种药材,每种药材都有无限多,并且知道每种药材的重量w(1<=w<=m)及价值v(1<=v<=100000),如何选择,才能使得采到的药材的总价值最大?

Input

第1行为两个整数m和n,分别为背包的容量及药材的种数。 第2至n+1行每行两个整数w和v,分别表示每种药材的重量及价值。

Output

能采到的药材的最大总价值

Sample Input

100 5
77 92
33 50
34 60
50 46
99 161

Sample Output

161


源代码(已经AC过的)

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
    int m,n;
    int c[10005],v[10005],f[10005];
    scanf("%d %d",&m,&n);
    memset(c,0,sizeof(c));
    memset(v,0,sizeof(v));
    memset(f,0,sizeof(f));
    for(int i=0;i<n;i++)
    {
       scanf("%d %d",&c[i],&v[i]);
    }
    for(int i=0;i<n;i++)
    {
        for(int j=c[i];j<=m;j++)
        {
            if(f[j-c[i]]+v[i]>f[j])
            {
                f[j]=f[j-c[i]]+v[i];
            }

        }
    }
    cout<<f[m]<<'\n';
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值