背包問題(5):混合背包

来源:https://www.cnblogs.com/cs-whut/archive/2022/04/03/16095050.html
-Advertisement-
Play Games

本篇是根據 GopherCon SG 2019 “Understanding Allocations” 演講的學習筆記。 Understanding Allocations: the Stack and the Heap - GopherCon SG 2019 - YouTube 理解分配:棧和堆 ...


        混合背包就是將前面三種基本的背包問題疊加成較複雜的問題。也就是說,有的物品只可以取一次(0/1背包),有的物品可以取無限次(完全背包),有的物品可以取的次數有一個上限(多重背包)。

        0/1背包與完全背包的混合比較簡單。如果只有兩類物品:一類物品只能取一次,另一類物品可以取無限次,那麼只需在對每個物品應用轉移方程時,根據物品的類別選用逆序(0/1背包)或順序(完全背包)的迴圈即可。

       一般可以編寫如下的迴圈。

​       for (i=1;i<=N;i++)                                            // 裝入背包的物品個數為N

       {

              if  (第i件物品只能取1次)                         //  0/1背包

                      for (j=V;j>=W[i];j--)                         //  0/1背包按由大到小順序枚舉重量

                           f[j]=max(f[j],f[j-W[i]]+P[i]);

            else                                                         // 第i件物品可以取無數次,完全背包

                      for ( j=W[i];j<=V;j++)                     // 完全背包按由小到大順序枚舉重量

                          f[j]=max(f[j],f[j-W[i]]+P[i]);   

        }

        如果再加上多重背包,一般可以編寫如下迴圈程式。

​         for (i=1;i<=N;i++)           // 裝入背包的物品個數為N

        {

               if  (第i件物品只能取1次)                   //  0/1背包

              {

                      for (j=V;j>=W[i];j--)  

                            f[j]=max(f[j],f[j-W[i]]+P[i]);

              }

               else  if  (第i件物品可以取無數次)     // 完全背包

              {

                      for ( j=W[i];j<=V;j++)    

                             f[j]=max(f[j],f[j-W[i]]+P[i]);  

             }

            else  if  (第i件物品可以取c[i]次)           // 多重背包

            {

             for (j=V; j>=0; j--)  

                 for (k=0; k<=c[i] &&  k*W[i] <=j; k++)

                      f[j] = max( f[j], f[j - k * W[i]] + k *P[i]);

             }

        } 

【例1】最多的糖果數

問題描述

今天是CRB的生日。他媽媽決定給她可愛的兒子買很多禮物。

她帶著M元(貨幣單位)去了最近的商店。

商店裡有N種禮物。買一件第i種禮物要花Wi元。

但由於商店的老闆是她的朋友,如果她買了x(x>0)件第i種禮物,花費x×Wi元,老闆會送給她Ai×x+Bi顆糖果。

她想得到最多的糖果。你的任務是幫助她。

輸入

有多個測試用例。輸入的第一行包含一個整數T(1≤ T≤ 20),表示測試用例的數量。

對於每個測試用例:第一行包含兩個整數M(1≤ M≤ 2000)和N(1≤ N≤ 1000)。接下來是N行,第i行包含三個空格分隔的整數Wi(1≤Wi≤2000)、Ai和Bi(0≤Ai,Bi≤2000)。

輸出

對於每個測試用例,輸出她能獲得的最大糖果。

輸入樣例

1

100 2

10 2 1

20 1 1

輸出樣例

21

        (1)編程思路。

         對於第i件商品,如果只買1個,得到的價值是Ai+Bi;如果在買1個的基礎上再買,得到的價值就是Ai。也就是說,第i件商品,除了第一次得到Ai+Bi顆糖果,以後購買都只得到Ai顆糖果,這樣可以將第i件商品看成兩種商品,其中兩種商品的購買代價都是Wi,第一種的價值是Ai+Bi,但是只允許買一次;第二種的價值是Ai,可以無限次購買。第一種商品用0/1背包處理,第二種商品用完全背包處理,並且先進行0/1背包,再進行完全背包。由於第一種商品的價值Ai+Bi大於或等於第二種商品的價值,付出相同的代價,價值大的肯定會被先考慮,也就是在用完全背包處理第二種商品時,第一種商品肯定已經被添加到背包里了。

        (2)源程式。

#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int f[2005];
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int m,n;
        scanf("%d%d", &m, &n);
        memset(f,0,sizeof(f));
        int w, a, b;
        int i,j;
        for (i = 1; i <=n; i++)
        {
            scanf("%d%d%d", &w, &a, &b);
            for (j= m; j >= w; j--)    // 每個商品的第1件只買1次,0/1背包
                f[j] = max(f[j], f[j-w] + a + b);
            for (j = w; j<=m; j++)    // 每個商品之後可購買次數不限,完全背包
                f[j] = max(f[j], f[j-w] + a);
        }
        int ans = 0;
        for (i=0; i<=m; i++)
            ans = max(ans, f[i]);
        printf("%d\n", ans);
    }
    return 0;
}

        將上面的源程式提交給HDU題庫HDU 5410 CRB and His Birthday(http://acm.hdu.edu.cn/showproblem.php?pid=5410),測評結果為Accepted

 【例2】櫻花

題目描述

愛與愁大神後院里種了n棵櫻花樹,每棵都有美學值 Ci (0≤Ci≤200)。愛與愁大神在每天上學前都會來賞花。愛與愁大神可是生物學霸,他懂得如何欣賞櫻花:一種櫻花樹看一遍過,一種櫻花樹最多看 Ai (0≤Ai≤100)遍,一種櫻花樹可以看無數遍。但是看每棵櫻花樹都有一定的時間Ti (0≤Ti≤100)。愛與愁大神離去上學的時間只剩下一小會兒了。求解看哪幾棵櫻花樹能使美學值最高且愛與愁大神能準時(或提早)去上學。

輸入

共 n+1行:

第1行:現在時間 Ts(幾時:幾分),去上學的時間 Te(幾時:幾分),愛與愁大神院子里有幾棵櫻花樹 n。這裡的Ts,Te格式為:hh:mm,其中0≤hh≤23,0≤mm≤59,且 hh,mm,n均為正整數。開始時間距離結束時間不超過 1000分鐘,n≤10000。

第2行到第n+1 行,每行三個正整數:看完第i棵樹的耗費時間Ti,第i棵樹的美學值Ci,看第i棵樹的次數Pi(Pi =0 表示無數次,Pi是其他數字表示最多可看的次數Pi)。

輸出

只有一個整數,表示最大美學值。

輸入樣例

6:50 7:00 3

2 1 0

3 3 1

4 5 4

輸出樣例

11

樣例解釋

賞第一棵櫻花樹一次,賞第三棵櫻花樹2次。

        (1)編程思路1。

        一種櫻花樹看一遍過(0/1背包),一種櫻花樹最多看 Ai (0≤Ai≤100)遍(多重背包),一種櫻花樹可以看無數遍(完全背包)。

        設現在時間為h1:m1,去上學的時間為h2:m2,則limt=h2*60+m2-(h1*60+m1)就是背包的容量,將看櫻花耗費的時間ti看成物品的重量,櫻花的美學值看成物品的價值,按上面的介紹,3種背包混合時分別處理即可。

        (2)源程式1。

#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int n,h1,m1,h2,m2;
    scanf("%d:%d %d:%d %d",&h1,&m1,&h2,&m2,&n);
    int limt=h2*60+m2-(h1*60+m1);
    int f[1005]={0};
    int i,j,k;
    for (i=1;i<=n;i++)
    {
        int t,c,p;
        scanf("%d%d%d",&t,&c,&p);
        if  (p==1)          //   看一次,0/1背包
        {
            for (j=limt;j>=t;j--)
                f[j]=max(f[j],f[j-t]+c);
        }
        else  if  (p==0)    // 看無數次,完全背包
        {
            for (j=t;j<=limt;j++)
                  f[j]=max(f[j],f[j-t]+c);
        }
        else               // 看pi次,多重背包
        {
             for (j=limt; j>=0; j--)
                 for (k=0; k<=p &&  k*t <=j; k++)
                      f[j] = max( f[j], f[j-k*t] + k *c);
        }
    }
    printf("%d\n",f[limt]);
    return 0;
}

        (3)編程思路2。

        可以將完全背包和多重背包均轉化為0/1背包求解。櫻花可以看無數遍,實際上由於背包容量的限制,最多也只能看limt/t遍,因此完全背包可看成一個物品取limt/t的多重背包,採用二進位拆分優化的方法將多重背包轉換為0/1背包統一求解。

        (4)源程式2。

#include <stdio.h>
#include <string.h>
int t[1000005],c[1000005];
int main()
{
    int n,h1,m1,h2,m2;
    scanf("%d:%d %d:%d %d",&h1,&m1,&h2,&m2,&n);
    int limt=h2*60+m2-(h1*60+m1);
    int i,j;
    int cnt=0;
    for (i=1;i<=n;i++)
    {
        int a,b,p;
        scanf("%d%d%d",&a,&b,&p);
        if (p==0) p=p=limt/a;
        for (j=1;j<=p;j<<=1)
        {
            t[++cnt]=j*a;
            c[cnt]=j*b;
            p-=j;
        }
        if (p)
        {
            t[++cnt]=a*p;
            c[cnt]=b*p;
        }
    }
    int f[1005];
    memset(f,0,sizeof(f));
    for (i=1;i<=cnt;i++)
        for (j=limt;j>=t[i];j--)
            if (f[j]<f[j-t[i]]+c[i]) f[j]=f[j-t[i]]+c[i];
    printf("%d\n",f[limt]);
    return 0;
}

        將上面的源程式提交給洛谷題庫P1833 櫻花(https://www.luogu.com.cn/problem/P1833),測評結果為Accepted

練習題

1.P1782 旅行商的背包(https://www.luogu.com.cn/problem/P1782

#include <stdio.h>
long long max(long long a,long long b)
{
    return a>b?a:b;
}
int main()
{
    long long n,m,C;
    scanf("%lld%lld%lld",&n,&m,&C);
    long long f[10005]={0};
    long long i,j,k;
    for (i=1;i<=n;i++)          // n種物品多重背包
    {
        long long v,w,d;
        scanf("%lld%lld%lld",&v,&w,&d);
        if (v*d>=C)
        {
           for (j=v;j<=C;j++)
              f[j]=max(f[j],f[j-v]+w);
        }
        else
        {
           k=1;
           while (k<d)
           {
              for (j=C;j>=k*v;j--)
                   f[j]=max(f[j],f[j-k*v]+k*w);
              d-=k;
              k*=2;
           }
           if (d>0)
           {
               for (j=C;j>=d*v;j--)
                  f[j]=max(f[j],f[j-d*v]+d*w);
           }
        }
    }
    for (i=1;i<=m;i++)         // m件奇貨是0/1背包
    {
        long long a,b,c;
        scanf("%lld%lld%lld",&a,&b,&c);
        for (j=C;j>=0;j--)
            for (k=0;k<=j;k++)
                f[j]=max(f[j],f[j-k]+(a*k+b)*k+c);
    }
    printf("%lld\n",f[C]);
    return 0;
}
View Code

2.P2851 [USACO06DEC]The Fewest Coins G(https://www.luogu.com.cn/problem/P2851)

#include <stdio.h>
#include <string.h>
#define INF 0x3f3f3f3f
int min(int a,int b)
{
    return a<b?a:b;
}
int main()
{
    int f1[25000];     // 約翰對不同金額所付的最少硬幣數量
    int f2[25000];     // 店家對不同金額找零的最少硬幣數量
    int n,t;
    scanf("%d%d",&n,&t);
    int sum=0;
    int i,j;
    int c[105],v[105];
    for (i=0;i<n;i++)
    {
         scanf("%d",&v[i]);
         if (sum<v[i]) sum=v[i];
    }
    for (i=0;i<n;i++)
         scanf("%d",&c[i]);
    sum=sum*sum+t+1;
    memset(f1,INF,sizeof(f1));
    memset(f2,INF,sizeof(f2));
    f1[0]=0;
    f2[0]=0;
    for (i=0;i<n;i++)
    {
        for (j=v[i];j<=sum;j++)      // 店家找零是完全背包
        {
            f2[j]=min(f2[j],f2[j-v[i]]+1);
        }
        if (c[i]*v[i]>=sum)          // 約翰付賬是多重背包
        {
           for (j=v[i];j<=sum;j++)
              f1[j]=min(f1[j],f1[j-v[i]]+1);
        }
        else
        {
           int k=1;
           int temp=c[i];
           while (k<temp)
           {
              for (j=sum;j>=k*v[i];j--)
                   f1[j]=min(f1[j],f1[j-k*v[i]]+k);
              temp-=k;
              k*=2;
           }
           for (j=sum;j>=temp*v[i];j--)
              f1[j]=min(f1[j],f1[j-temp*v[i]]+temp);
        }
    }
    int res=INF;
    for (i=t;i<=sum;i++)
    {
         res=min(res,f1[i]+f2[i-t]);
    }
    if (res==INF)
        printf("-1\n");
    else
        printf("%d\n",res);
    return 0;
}
View Code
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 用戶流失了,觸達難? 活動做了那麼多,轉化仍然很低? 運營也需要提前思考,預測用戶動向,提前精準觸達,才能事半功倍。結合HMS Core分析服務的預測服務和智能運營,洞察營銷時機,實時落地營銷策略,提升用戶運營效率。 預測服務擁有精準預測模型和深度人群洞察,支持查看近一周的預測準確率,幫助運營者做出 ...
  • flex三連問,幫助我們更好的理解佈局利器 問題: flex的值 auto, none, 0, 1, initial分別是什麼?有什麼作用?有什麼表現? flex-basis和width的區別?單值flex-basis:0與auto的區別?flex-basis:100px與width:100px一樣 ...
  • 前言 小學數學老師教過我們,0.1 + 0.2 = 0.3,但是為什麼在我們在瀏覽器的控制臺中輸出卻是0.30000000000000004? 除了加法有這個奇怪的現象,帶小數點的減法和乘除計算也會得出意料之外的結果 console.log(0.3 - 0.1) // 0.1999999999999 ...
  • 註意如果你的mac是M1處理器 那抱歉當前文章可能不支持了,因為當前模擬器不支持。 3步完成mac uniapp 模擬器配置 1.下載網易mumu模擬器 https://mumu.163.com/mac/index.html 2.安裝 設置 下載完成後安裝運行就是這樣的 選擇屏幕旋轉 手機模式 3. ...
  • 具體示例 //代碼 console.log(JSON.stringify({ x: 5, y: 6 },null,2)); //輸出結果 { "x": 5, "y": 6 } JSON.stringify() 介紹 JSON.stringify()方法將一個JavaScript對象或值轉換為JSON ...
  • 原型模式不是通過new生成新的對象,而使通過複製進行生成; 原型模式適用於相同類型的多個對象的生成; 原型模式分為兩種:淺克隆/淺表副本(Shallow Clone)和深克隆/深表副本(Deep Clone); 淺克隆:Shallow Clone,只複製值類型變數,不複製引用類型變數的克隆;(只複製 ...
  • 相比於工廠模式,抽象工廠模式的每個工廠可以創建產品系列,而不是一個產品; 抽象工廠用到的技術:介面、多態、配置文件、反射; 抽象工廠模式的設計原則: 實現客戶端創建產品和使用產品的分離,客戶端無須瞭解創建的細節,符合迪米特法則; 客戶端面向介面定義產品,符合依賴倒置原則; 客戶端面向介面定義工廠,而 ...
  • 外觀(Facade)模式,又叫做門面模式,是一種通過為多個複雜的子系統提供一個一致的介面,使這些子系統更加容易被訪問的模式。比如說我們日常生活中醫院的分診台,就是實現統一訪問介面的特性: 一、外觀模式介紹 外觀模式提供一個統一介面,用來訪問子系統的一系列介面,從而讓子系統更容易使用。這個子系統可以有 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...