背包問題(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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...