【算法】背包九講


背包九講板子

例題參考《信息學奧賽一本通》

初始化分兩種情況
1、如果背包要求正好裝滿則初始化 f[0] = 0, f[1~v] = -INF;
2、如果不需要正好裝滿 f[0~v] = 0;


01背包

有N件物品和一個容量為V的背包。第i件物品的費用(即體積,下同)是w[i],價值是c[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

例題
【問題描述】
一個旅行者有一個最多能用m公斤的背包,現在有n件物品,它們的重量分別是W1,W2,...,Wn,它們的價值分別為C1,C2,...,Cn.若每種物品只有一件求旅行者能獲得最大總價值。
【輸入格式】
第一行:兩個整數,M(背包容量,M<=200)和N(物品數量,N<=30);
第2..N+1行:每行二個整數Wi,Ci,表示每個物品的重量和價值。
【輸出格式】
僅一行,一個數,表示最大總價值。
【樣例輸入】package.in
10 4
2 1
3 3
4 5
7 9
【樣例輸出】package.out
12

  • 代碼

解法一

#include<cstdio>
using namespace std;
const int maxn=201,maxn=31;
int m,n;
int w[maxn],c[maxn];
int f[maxn][maxm]; 
int max(int x,int y)  { x>y?x:y;}                     //求x和y最大值
int main()
{
    scanf("%d%d",&m,&n);         //背包容量m和物品數量n
    for (int i=1;i<=n;i++)         //在初始化循環變量部分,定義一個變量並初始化
    scanf("%d%d",&w[i],&c[i]);    //每個物品的重量和價值
    for(int i=1;i<=n;i++)         // f[i][v]表示前i件物品,總重量不超過v的最優價值
    for(int v=m;v>0;v--)
    if(w[i]<=v)  
	f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
    else
	f[i][v]=f[i-1][v];
    printf("%d",f[n][m]);           // f[n][m]為最優解
    return 0;
}

解法二

#include<cstdio>
using namespace std;
const int maxm=2001,maxn=31;
int m,n;
int w[maxn],c[maxn];
int f[maxm]; 
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品數量n
    for (int i=1;i<=n;i++)
    scanf("%d%d",&w[i],&c[i]);     //每個物品的重量和價值
    for(int i=1;i<=n;i++)             //設f(v)表示重量不超過v公斤的最大價值
    for(int v=m;v>=w[i];v--)
    if(f[v-w[i]]+c[i]>f[v])
    f[v]=f[v-w[i]]+c[i];
    printf("%d",f[m]);                      // f(m)為最優解
    return 0;
}


完全背包

有N種物品和一個容量為V的背包,每種物品都有無限件可用。第i種物品的費用是w[i],價值是c[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

  • 例題
    【問題描述】
      設有n種物品,每種物品有一個重量及一個價值。但每種物品的數量是無限的,同時有一個背包,最大載重量為M,今從n種物品中選取若干件(同一種物品可以多次選取),使其重量的和小於等於M,而價值的和為最大。
    【輸入格式】
    第一行:兩個整數,M(背包容量,M<=200)和N(物品數量,N<=30);
    第2..N+1行:每行二個整數Wi,Ci,表示每個物品的重量和價值。
    【輸出格式】
    僅一行,一個數,表示最大總價值。
    【樣例輸入】knapsack.in
    10 4
    2 1
    3 3
    4 5
    7 9
    【樣例輸出】knapsack.out
      max=12

  • 代碼
    解法一

#include<cstdio>
using namespace std;
const int maxm=201,maxn=31;
int m, n;
int w[maxn],c[maxn];
int f[maxn][maxm]; 
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品數量n
    for(int i=1;i<=n;i++) 
    scanf("%d%d",&w[i],&c[i]);    //每個物品的重量和價值
    for(int i=1;i<=n;i++)            //f[i][v]表示前i件物品,總重量不超過v的最優價值
    for(int v=1;v<=m;v++)
    if(v < w[i])  
	f[i][v]=f[i-1][v];
    else
  if(f[i-1][v]>f[i][v-w[i]]+c[i])
    f[i][v]=f[i-1][v];
    else 
	f[i][v]=f[i][v-w[i]]+c[i]; 
    printf("max=%d",f[n][m]);         // f[n][m]為最優解
    return 0;
}

解法二

#include<cstdio>
using namespace std;
const int maxm=2001,maxn=31;
int n,m,v,i;
int c[maxn],w[maxn];
int f[maxm];
int main()
{
    scanf("%d%d",&m,&n);            //背包容量m和物品數量n
    for(i=1;i<=n;i++) 
    scanf("%d%d",&w[i],&c[i]);
    for(i=1;i<=n;i++)
    for(v=w[i];v<=m;v++)          //設 f[v]表示重量不超過v公斤的最大價值
    if(f[v-w[i]]+c[i]>f[v])
	f[v]=f[v-w[i]]+c[i];
    printf("max=%d\n",f[m]);           // f[m]為最優解
    return 0;
}



多重背包

有N種物品和一個容量為V的背包。第i種物品最多有n[i]件可用,每件費用是w[i],價值是c[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

  • 例題
    慶功會
    【問題描述】
    為了慶賀班級在校運動會上取得全校第一名成績,班主任決定開一場慶功會,為此撥款購買獎品犒勞運動員。期望撥款金額能購買最大價值的獎品,可以補充他們的精力和體力。
    【輸入格式】
    第一行二個數n(n<=500),m(m<=6000),其中n代表希望購買的獎品的種數,m表示撥款金額。
    接下來n行,每行3個數,v、w、s,分別表示第I種獎品的價格、價值(價格與價值是不同的概念)和購買的數量(買0件到s件均可),其中v<=100,w<=1000,s<=10。
    【輸出格式】
    第一行:一個數,表示此次購買能獲得的最大的價值(注意!不是價格)。
    【輸入樣例】
    5 1000
    80 20 4
    40 50 9
    30 50 7
    40 30 6
    20 20 1
    【輸出樣例】
    1040
  • 代碼
    解法一
#include<iostream>
using namespace std;
int n,m;
int f[6002];
int v[6002],w[6002],s[6002];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i]>>s[i];
	for(int i=1;i<=n;i++)
	for(int j=m;j>=0;j--)
	for(int k=0;k<=s[i];k++)
	{
		if(j-k*v[i]<0)
		break;
		f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
	}
	cout<<f[m];
}

解法二(二進制優化)

#include<iostream>
using namespace std;
int n,m;
int f[6002];
int v[6002],w[6002];
int v1,w1,s;
int n1=0;
int main()
{
	cin>>n>>m;
	while(n--)//把每種划分成不同種類的 
	{
		cin>>v1>>w1>>s;
		for(int i=1;i<=s;i*=2)//二進制分解 
		//把14分成1,2,4,6多出1在后面打包 
		{
			v[++n1]=i*v1;
			w[n1]=i*w1;
			s-=i;
		}
		if(s>0)//多出來的一起打包 
		{
			v[++n1]=s*v1;
			w[n1]=s*w1;
		}
	}
	for(int i=1;i<=n1;i++)
	for(int j=m;j>=v[i];j--)
	{
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	cout<<f[m];
}

混合背包

將01背包、完全背包、多重背包混合起來。也就是說,有的物品只可以取一次(01背包),有的物品可以取無限次(完全背包),有的物品可以取的次數有一個上限(多重背包)。

  • 例題
    【問題描述】
    一個旅行者有一個最多能用V公斤的背包,現在有n件物品,它們的重量分別是W1,W2,...,Wn,它們的價值分別為C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取無限次(完全背包),有的物品可以取的次數有一個上限(多重背包)。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
    【輸入格式】
    第一行:二個整數,V(背包容量,V<=200),N(物品數量,N<=30);
    第2..N+1行:每行三個整數Wi,Ci,Pi,前兩個整數分別表示每個物品的重量,價值,第三個整數若為0,則說明此物品可以購買無數件,若為其他數字,則為此物品可購買的最多件數(Pi)。
    【輸出格式】
    僅一行,一個數,表示最大總價值。
    【樣例輸入】mix.in
    10 4
    2 1 0
    3 3 1
    4 5 4
    【樣例輸出】mix.out
    11
    【樣例解釋】
    選第一件物品1件和第三件物品2件。
  • 代碼
#include<cstdio>
using namespace std;
int m,n;
int w[31],c[31],p[31];
int f[201];
int max(int x,int y)
{  
	return
    x>y?x:y; 
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
    scanf("%d%d%d",&w[i],&c[i],&p[i]);
    for(int i=1;i<=n;i++)
    if(p[i]==0)
	{                                   //完全背包
        for(int j=w[i];j<=m;j++)
        f[j]=max(f[j],f[j-w[i]]+c[i]);
    }
    else
	{
        for(int j=1;j<=p[i];j++)           //01背包和多重背包
        for(int k=m;k>=w[i];k--)
		f[k]=max(f[k],f[k-w[i]]+c[i]);
    }
    printf("%d",f[m]); 
    return 0;
}


二維費用背包

二維費用的背包問題是指:對於每件物品,具有兩種不同的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(背包容量)。問怎樣選擇物品可以得到最大的價值。設這兩種代價分別為代價1和代價2,第i件物品所需的兩種代價分別為a[i]和b[i]。兩種代價可付出的最大值(兩種背包容量)分別為V和U。物品的價值為c[i]。

  • 例題
    【問題描述】
    潛水員為了潛水要使用特殊的裝備。他有一個帶2種氣體的氣缸:一個為氧氣,一個為氮氣。讓潛水員下潛的深度需要各種的數量的氧和氮。潛水員有一定數量的氣缸。每個氣缸都有重量和氣體容量。潛水員為了完成他的工作需要特定數量的氧和氮。他完成工作所需氣缸的總重的最低限度的是多少?
    例如:潛水員有5個氣缸。每行三個數字為:氧,氮的(升)量和氣缸的重量:
    3 36 120
    10 25 129
    5 50 250
    1 45 130
    4 20 119
    如果潛水員需要5升的氧和60升的氮則總重最小為249(1,2或者4,5號氣缸)。
    你的任務就是計算潛水員為了完成他的工作需要的氣缸的重量的最低值。
    【輸入格式】
    第一行有2整數m,n(1<=m<=21,1<=n<=79)。它們表示氧,氮各自需要的量。
    第二行為整數k(1<=n<=1000)表示氣缸的個數。
    此后的k行,每行包括ai,bi,ci(1<=ai<=21,1<=bi<=79,1<=ci<=800)3整數。這些各自是:第i個氣缸里的氧和氮的容量及汽缸重量。
    【輸出格式】
    僅一行包含一個整數,為潛水員完成工作所需的氣缸的重量總和的最低值。
  • 代碼
#include<cstdio>
#include<cstring>                                   //初始化memset要用到
using namespace std;
int v,u,k;
int a[1001],b[1001],c[1001];
int f[101][101];
int main()
{
    memset(f,127,sizeof(f));                    //初始化為一個很大的正整數
    f[0][0]=0;
    scanf("%d%d%d",&v,&u,&k);
    for(int i=1;i<=k;i++)
    scanf("%d%d%d",&a[i],&b[i],&c[i]);
    for(int i=1;i<=k;i++)
    for(int j=v;j>=0;j--)
    for(int l=u;l>=0;l--)
    {
       int t1=j+a[i],t2=l+b[i];
       if(t1>v)  
	   t1=v;                        //若氮、氧含量超過需求,可直接用需求量代換,
       if(t2>u)  
	   t2=u;                        //不影響最優解
       if(f[t1][t2]>f[j][l]+c[i])
	   f[t1][t2]=f[j][l]+c[i];
    }
    printf("%d",f[v][u]);
    return 0;
}


分組背包

有N件物品和一個容量為V的背包。第i件物品的費用是w[i],價值是c[i]。這些物品被划分為若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

  • 例題
    【問題描述】
    一個旅行者有一個最多能用V公斤的背包,現在有n件物品,它們的重量分別是W1,W2,...,Wn,它們的價值分別為C1,C2,...,Cn。這些物品被划分為若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
    【輸入格式】
    第一行:三個整數,V(背包容量,V<=200),N(物品數量,N<=30)和T(最大組號,T<=10);
    第2..N+1行:每行三個整數Wi,Ci,P,表示每個物品的重量,價值,所屬組號。
    【輸出格式】
    僅一行,一個數,表示最大總價值。
    【樣例輸入】group.in
    10 6 3
    2 1 1
    3 3 1
    4 8 2
    6 9 2
    2 8 3
    3 9 3
    【樣例輸出】group.out
    20

  • 代碼

#include<cstdio>
using namespace std;
int v,n,t;
int w[31],c[31];
int a[11][32],f[201];
int main()
{
    scanf("%d%d%d",&v,&n,&t);
    for(int i=1;i<=n;i++)
	{
      int p;
        scanf("%d%d%d",&w[i],&c[i],&p);
        a[p][++a[p][0]]=i;
    }
    for(int k=1;k<=t;k++)
    for(int j=v;j>=0;j--)
    for(int i=1;i<=a[k][0];i++)
    if (j >= w[a[k][i]]) 
	{
        int tmp=a[k][i];
        if(f[j]<f[j-w[tmp]]+c[tmp])
        f[j]=f[j-w[tmp]]+c[tmp]; 
    }
    printf("%d",f[v]);
    return 0;
}


有依賴的背包

這種背包問題的物品間存在某種“依賴”的關系。也就是說,i依賴於j,表示若選物品i,則必須選物品j。為了簡化起見,我們先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。
這個問題由NOIP2006金明的預算方案一題擴展而來。遵從該題的提法,將不依賴於別的物品的物品稱為“主件”,依賴於某主件的物品稱為“附件”。由這個問題的簡化條件可知所有的物品由若干主件和依賴於每個主件的一個附件集合組成。

思路

  1. 先把每個附件與主件分成同一組
  2. 把每一組做一個01背包(一定要選主件)
  3. 進行分組背包

#include<iostream>
using namespace std;
int n,m;
int v[80],p[80],q[80];
int s[40400],t[40400];//s數組存最終的答案 t數組存過渡答案 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>v[i]>>p[i]>>q[i];
        p[i]=v[i]*p[i];//先算出代價 
    }
    for(int i=1;i<=m;i++)
    {
        if(q[i]==0)
        {
            for(int j=1;j<=v[i];j++)
            t[j]=0;//比v[i]小的答案修改不了賦值成0 
            for(int j=v[i];j<=n;j++)
            {
                t[j]=s[j-v[i]]+p[i];//修改比v[i]大的答案 
            }
            for(int j=1;j<=m;j++)//尋找i的附件 
            {
                if(q[j]==i)//找到i的附件 
                {
                    for(int k=n;k>=v[i]+v[j];k--)//必須要取主件所以k>=v[i]+v[j](分組背包思想) 
                    {
                        t[k]=max(t[k],t[k-v[j]]+p[j]);//01背包把每組打包
                    }
                }
                for(int k=v[i];k<=n;k++) 
                if(t[k]>s[k]) 
                s[k]=t[k];//替換有比原有答案大的新答案 
            }
        }
    }
    cout<<s[n];
}

背包問題的方案總數

對於一個給定了背包容量、物品費用、物品間相互關系(分組、依賴等)的背包問題,除了再給定每個物品的價值后求可得到的最大價值外,還可以得到裝滿背包或將背包裝至某一指定容量的方案總數。
對於這類改變問法的問題,一般只需將狀態轉移方程中的max改成sum即可。例如若每件物品均是01背包中的物品,轉移方程即為f[i][v]=sum{f[i-1][v],f[i-1][v-w[i]]+c[i]},初始條件f[0][0]=1。
  事實上,這樣做可行的原因在於狀態轉移方程已經考察了所有可能的背包組成方案。

  • 例題

【問題描述】
  給你一個n種面值的貨幣系統,求組成面值為m的貨幣有多少種方案。樣例:設n=3,m=10,要求輸入和輸出的格式如下:
【樣例輸入】money.in
3 10 //3種面值組成面值為10的方案
1 //面值1
2 //面值2
5 //面值5
【樣例輸出】money.out
  10 //有10種方案

  • 代碼
#include<cstdio>
int m,n;
int a[1001];
long long f[10001];                     //注意要用long long 
int main()
{
    scanf("%d%d",&n,&m);              //n種面值的貨幣,組成面值為m
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);               //輸入每一種面值
    f[0]=1;
    for(int i=1;i<=n;i++)
    for(int j=m;j>=a[i];j--)         //f[j]表示面值為j的總方案數
    for(int k=1;k<=j/a[i];k++)
    f[j]+=f[j-k*a[i]];
    printf("%lld",f[m]);                 // f[m]為最優解
    return 0;
}


一波瞎總結

總的來說背包問題並不難理解
主要是題目的靈活性需要考慮到底要使用什么背包
還有 每個背包的代碼基本相似
但卻有個別的差別
所以要搞清楚每個變量的定義
最好是要把代碼背下來考試時才不會want to go die


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM