acm算法之三大背包问题

来源:互联网 发布:win10优化游戏性能 编辑:程序博客网 时间:2024/06/08 08:39

三大背包问题

1.01背包问题

有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

状态转移方程:

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

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

伪码:

  for i=1..N

   for v=V..0

    f[v]=max{f[v],f[v-c[i]]+w[i]};

如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,

价值为f[i-1][v];

如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,

此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

 

例题:

Description

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

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

Input

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

Output

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

SampleInput

70 3

71 100

69 1

1 2

SampleOutput

3

解决方案:

#include<iostream>

# include<cstring>

# define max(a,b) a>b?a:b

using namespace std;

int main()

{

 

    int dp[101][1001],m,T,w[101],val[101],i,j;

    cin>>T>>m;

    for(i=1;i<=m;i++)

        cin>>w[i]>>val[i];

    memset(dp,0,sizeof(dp));

    for(i=1;i<=m;i++)

     for(j=0;j<=T;j++)      //j相当于上面说的V-c[i]

         {

    if(j>=w[i])

       dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+val[i]);//放还是不放的选择

    else dp[i][j]=dp[i-1][j];

     }

     cout<<dp[m][T]<<endl;

     return 0;

}

*01背包问题的一维优化

普通01背包问题解决方案花费如下:

     时间复杂度为o(V * T) ,空间复杂度为o(V * T) 。 时间复杂度已经无法优化,但是空间复杂度则可以进行优化。但必须将V 递减的方式进行遍历,即V.......0 的方式进行。

初始化:

(1)若要求背包必须放满,则初始如下:

        f[0] = 0 , f[1...V]表示-INF。表示当容积为0时,只接受一个容积为0的物品入包。

(2)若要求背包可以空下,则初始化如下:

        f[0...V] = 0 ,表示任意容积的背包都有一个有效解即为0。   

具体解释如下:

     初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。

     如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,

     其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。

     如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,

     这个解的价值为0,所以初始时状态的值也就全部为0了。

相关代码及解释:

/*

 01背包,使用了优化后的存储空间

 建立数组

   f[i][v] = max(f[i-1][v]  , f[i-1][v-c[i]] + w[i]) 

    将前i件物品,放入容量为v的背包中的最大值。

下面介绍一个优化,使用一维数组,来表示

(1) f[v]表示每一种类型的物品,在容量为v的情况下,最大值。

    但是体积循环的时候,需要从v----1循环递减。

初始化问题:

(1)若要求背包中不允许有剩余空间,则可以将f[0]均初始化为0,其余的f[1..n]均初始化为-INF 。

    表示只有当容积为0 的时候,允许放入质量为0的物品。

    而当容积不为0的情况下,不允许放入质量为0的物品,并且把状态置为未知状态。  

(2)若要求背包中允许有剩余空间 ,则可以将f[1n],均初始化为0。

   这样,当放不下去的时候,可以空着。

&/

#include <iostream>

 using namespace std ;

 const int V = 1000 ;  //总的体积

 const int T = 5 ;    //物品的种类

 int f[V+1] ;

 //#define EMPTY                          //可以不装满

 int w[T] = {8 , 10 , 4 , 5 , 5};         //价值

 int c[T] = {600 , 400 , 200 , 200 , 300};//每一个的体积

 const int INF = -66536  ;

 

 int package()

 {

    #ifdefEMPTY

    for(int i = 0 ; i <= V ;i++) //条件编译,表示背包可以不存储满

      f[i] = 0 ;   

 #else

    f[0] = 0 ;

    for(int i = 1 ; i <= V ;i++) //条件编译,表示背包必须全部存储满

      f[i] = INF ;  

 #endif

   

    for(int i = 0 ; i < T ; i++)

    {

      for(int v = V ; v >= c[i] ;v--)      //必须全部从V递减到0

         {             

           f[v] = max(f[v-c[i]] + w[i] ,f[v])  ;

//此f[v]实质上是表示的是i-1次之前的值。

         }                

    }

    return f[V] ;       

 }

 

 int main()

 {

     

   int temp = package() ;  

   cout<<temp<<endl     ;  

   system("pause")      ;

   return 0 ;   

 }

例题:

Description

Many years ago , in Teddy’shometown there was a man who was called “Bone Collector”. This man like tocollect varies of bones , such as dog’s , cow’s , also he went to the grave …

The bone collector had a bigbag with a volume of V ,and along his trip of collecting there are a lot ofbones , obviously , different bone has different value and different volume,now given the each bone’s value along his trip , can you calculate out themaximum of the total value the bone collector can get ?

Input

The first line contain ainteger T , the number of cases.

Followed by T cases , eachcase three lines , the first line contain two integer N , V, (N <= 1000 , V<= 1000 )representing the number of bones and the volume of his bag. And thesecond line contain N integers representing the value of each bone. The thirdline contain N integers representing the volume of each bone.

Output

One integer per linerepresenting the maximum of the total value (this number will be less than231).

SampleInput

1

5 10

1 2 3 4 5

5 4 3 2 1

SampleOutput

14

代码:

#include <iostream>

using namespace std;

 int main()

{

int nCases;

int nPack, nMaxVolume;

int weight[1002], value[1002];

int record[1002];

    //freopen("input.txt","r", stdin);

    scanf("%d", &nCases);

    while(nCases--)

    {

        memset(record, 0, sizeof(record));

        scanf("%d %d", &nPack,&nMaxVolume);

        for(int i=0; i<nPack; ++i)

            scanf("%d",&value[i]);

        for(int i=0; i<nPack; ++i)

            scanf("%d",&weight[i]);

        for(int i=0; i<nPack; ++i)

            for(int j=nMaxVolume; j>=weight[i]; --j)

            {

                if(record[j-weight[i]]+value[i]> record[j])

                    record[j] =record[j-weight[i]]+value[i];

            }

        printf("%d\n",record[nMaxVolume]);

    }

    return 0;

}

2.多重背包

有N种物品和一个容量为V的背包。第i种物品最多有num[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装

入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

状态转移方程为:

例题

Description

急!灾区的食物依然短缺!

为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?

Input

输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

Output

对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

SampleInput

1

8 2

2 100 4

4 100 2

SampleOutput

400

解决方案:

#include <iostream>

using namespace std;

int nCases;

int nValue, nKind;

int value[105], weight[105],bag[105];

int nMultiplePack[105];

int main()

{

    //freopen("input.txt","r", stdin);

    scanf("%d", &nCases);

    while(nCases--)

    {

        memset(nMultiplePack, 0,sizeof(nMultiplePack));

        scanf("%d %d", &nValue,&nKind);

        for(int i=0; i<nKind; ++i)

            scanf("%d %d %d",&value[i], &weight[i], &bag[i]);

        for(int i=0; i<nKind; ++i)

            for(int j=0; j<bag[i]; ++j)

                for(int k=nValue;k>=value[i]; --k)

                    if(nMultiplePack[k] <nMultiplePack[k-value[i]]+weight[i])

                        nMultiplePack[k] =nMultiplePack[k-value[i]] + weight[i];

        printf("%d\n",nMultiplePack[nValue]);

    }

    return 0;

}

3.完全背包

问题:一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],每个物品都有无限多件,现在往背包里面装东西,怎么装能使背包的内物品价值最大?

状态转移方程为:

f[i+1][j]=max(f[i][j-k*weight[i+1]+k*value[i+1]),其中0<=k<=V/weight[i+1]

解决方案:

#include<iostream> 

using namespace std; 

#define  V 1500 

unsigned int f[V];//全局变量,自动初始化为0 

unsigned int weight[10]; 

unsigned int value[10]; 

#define  max(x,y)  (x)>(y)?(x):(y) 

int main() 

    int N,M; 

    cin>>N;//物品个数 

    cin>>M;//背包容量 

    for (int i=1;i<=N; i++) 

    { 

       cin>>weight[i]>>value[i]; 

    } 

    for (int i=1; i<=N; i++) 

        for (int j=1; j<=M; j++) 

        { 

            if (weight[i]<=j) 

            { 

               f[j]=max(f[j],f[j-weight[i]]+value[i]); 

            }            

        } 

cout<<f[M]<<endl;//输出最优解 

return 0;

例题:

Description

Before ACM can do anything, abudget must be prepared and the necessary financial support obtained. The mainincome for this action comes from Irreversibly Bound Money (IBM). The ideabehind is simple. Whenever some ACM member has any small money, he takes allthe coins and throws them into a piggy-bank. You know that this process isirreversible, the coins cannot be removed without breaking the pig. After asufficiently long time, there should be enough cash in the piggy-bank to payeverything that needs to be paid.

But there is a big problemwith piggy-banks. It is not possible to determine how much money is inside. Sowe might break the pig into pieces only to find out that there is not enoughmoney. Clearly, we want to avoid this unpleasant situation. The onlypossibility is to weigh the piggy-bank and try to guess how many coins areinside. Assume that we are able to determine the weight of the pig exactly andthat we know the weights of all coins of a given currency. Then there is someminimum amount of money in the piggy-bank that we can guarantee. Your task isto find out this worst case and determine the minimum amount of cash inside thepiggy-bank. We need your help. No more prematurely broken pigs!

Input

The input consists of T testcases. The number of them (T) is given on the first line of the input file.Each test case begins with a line containing two integers E and F. Theyindicate the weight of an empty pig and of the pig filled with coins. Bothweights are given in grams. No pig will weigh more than 10 kg, that means 1<= E <= F <= 10000. On the second line of each test case, there is aninteger number N (1 <= N <= 500) that gives the number of various coinsused in the given currency. Following this are exactly N lines, each specifyingone coin type. These lines contain two integers each, Pand W (1 <= P <=50000, 1 <= W <=10000). P is the value of the coin in monetary units, Wis it's weight in grams.

Output

Print exactly one line ofoutput for each test case. The line must contain the sentence "The minimumamount of money in the piggy-bank is X." where X is the minimum amount ofmoney that can be achieved using coins with the given total weight. If theweight cannot be reached exactly, print a line "This is impossible.".

SampleInput

3

10 110

2

1 1

30 50

10 110

2

1 1

50 30

1 6

2

10 3

20 4

SampleOutput

The minimum amount of money inthe piggy-bank is 60.

The minimum amount of money inthe piggy-bank is 100.

This is impossible.

解决方案:

// 初始化为无穷大

#include <iostream>

using namespace std;

 int nCases;

int nPack, nVolume1, nVolume2,nVolume;

int weight[510], value[510];

int record[10000];

const int INF = 1000000001;

int main()

{   

    //freopen("input.txt","r", stdin);

     scanf("%d", &nCases);

    while(nCases--)

    {

        scanf("%d %d", &nVolume1,&nVolume2);

        nVolume = nVolume2-nVolume1;

        scanf("%d", &nPack);

        for(int i=0; i<nPack; ++i)

        {

            scanf("%d %d",&value[i], &weight[i]);

        }

        for(int i=0; i<=nVolume; ++i)

            record[i] = INF;

        record[0] = 0;

        for(int i=0; i<nPack; ++i)

            for(int j=weight[i]; j<=nVolume; ++j)

               if(record[j]>record[j-weight[i]]+value[i])

                    record[j] =record[j-weight[i]]+value[i];

        if(record[nVolume] == INF)

            printf("This isimpossible.\n");

        else

            printf("The minimum amount ofmoney in the piggy-bank is %d.\n",record[nVolume]);

     }

    return 0;

}

// 这是ambition神牛的版本

// 初始化为-1

 

#include<stdio.h>

int num[10001],w[500],v[500];

main()

{

    int n,m,e,f,t,i,j;

    for(scanf("%d",&t);t>0;t--)

    {

        scanf("%d%d",&e,&f);

        m=f-e;

       for(scanf("%d",&n),i=0;i<n;i++)

           scanf("%d%d",&v[i],&w[i]);

        num[0]=0;

        for(i=1;i<=m;i++)

            num[i]=-1;

        for(i=0;i<n;i++)

        {

            for(j=w[i];j<=m;j++)

            {

               if(num[j-w[i]]!=-1&&num[j]!=-1)

                {if(num[j-w[i]]+v[i]<num[j])num[j]=num[j-w[i]]+v[i];}

                elseif(num[j-w[i]]!=-1&&num[j]==-1)

                {num[j]=num[j-w[i]]+v[i];}

            }

        }

        if(num[m]!=-1)

        printf("The minimum amount ofmoney in the piggy-bank is %d.\n",num[m]);

        else

            printf("This isimpossible.\n");

    }

}

*总结

首先我们把三种情况放在一起来看:

01背包(ZeroOnePack): 有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

比较三个题目,会发现不同点在于每种背包的数量,01背包是每种只有一件,完全背包是每种无限件,而多重背包是每种有限件。

先来分析01背包:

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

用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

把这个过程理解下:在前i件物品放进容量v的背包时,

它有两种情况:

第一种是第i件不放进去,这时所得价值为:f[i-1][v]

第二种是第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]

(第二种是什么意思?就是如果第i件放进去,那么在容量v-c[i]里就要放进前i-1件物品)

最后比较第一种与第二种所得价值的大小,哪种相对大,f[i][v]的值就是哪种。

这里是用二位数组存储的,可以把空间优化,用一位数组存储。

用f[0..v]表示,f[v]表示把前i件物品放入容量为v的背包里得到的价值。把i从1~n(n件)循环后,最后f[v]表示所求最大值。

*这里f[v]就相当于二位数组的f[i][v]。那么,如何得到f[i-1][v]和f[i-1][v-c[i]]+w[i]

首先要知道,我们是通过i从1到n的循环来依次表示前i件物品存入的状态。即:for i=1..N现在思考如何能在是f[v]表示当前状态是容量为v的背包所得价值,而又使f[v]和f[v-c[i]]+w[i]标签前一状态的价值

逆序!这就是关键!

for i=1..N

   for v=V..0

        f[v]=max{f[v],f[v-c[i]]+w[i]};

分析上面的代码:当内循环是逆序时,就可以保证后一个f[v]和f[v-c[i]]+w[i]是前一状态的!

完全背包:

完全背包按其思路仍然可以用一个二维数组来写出:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

同样可以转换成一维数组来表示:伪代码如下:

for i=1..N

    for v=0..V

        f[v]=max{f[v],f[v-c[i]]+w[i]}

顺序!

想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。

现在关键的是考虑:为何完全背包可以这么写?

在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。

那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?

因为每种背包都是无限的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。

多重背包:

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

这里同样转换为01背包:普通的转换对于数量较多时,则可能会超时,可以转换成二进制(暂时不了解,所以先不讲)对于普通的。就是多了一个中间的循环,把j=0~bag[i],表示把第i中背包从取0件枚举到取bag[i]件。


0 0
原创粉丝点击