01揹包的模板
小提醒:寫01揹包時要養成 初始化數組 和 從1開始循環 的習慣
#無優化
for(int i=1;i<=n;i++)
{
for(int c=1;c<=m;c++) //在這裏,揹包放入物品後,容量不斷的減少,直到再也放不進了
{
f[i][c]=f[i-1][c];
if(c>=w[i])
f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]); // w爲體積 v爲價值
}
}
// f[n][m]即爲最大值(以物品爲第一維數組)
#一維數組與判斷條件優化
for(int i=1;i<=n;i++)
{
for(int c=m;c>=w[i];c--)
{
f[c]=max(f[c],f[c-w[i]]+v[i]); // w爲體積 v爲價值
}
}
// f[m]即爲最大值(以容量爲第一維數組)
經典例題1
樣例輸入
2
50 1
5
10
1 2 3 2 1 1 2 3 2 1
50
0
樣例輸出
-45
32
AC代碼
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int main()
{
while(~scanf("%d",&n),n>0)
{
int i,price[1005]= {0},dp[1005] = {0}; //dp要初始化爲 0
for(i = 1; i<=n; i++)
cin>>price[i];
sort(price+1,price+1+n);
int MAX=price[n];
int j,m;
cin>>m; //相當於揹包容量
if(m<5) //低於5元不能購買
{
cout<<m<<endl;
continue;
}
m-=5; //取出5元用於購買最貴的物品
for(i = 1; i<n; i++)//01揹包
{
for(j = m;j>=price[i];j--)
{
dp[j] = max(dp[j],dp[j-price[i]]+price[i]);
}
}
cout<<m+5-dp[m]-MAX<<endl;
}
return 0;
}
做法:首先要在現在所擁有的餘額中保留5元,用這五元去購買最貴的物品,而剩下的錢就是揹包的總容量,將容量減爲最小
經典例題2
樣例輸入
2
10 1
20 1
3
10 1
20 2
30 1
-1
樣例輸出
20 10
40 40
AC代碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int val[5005]; //50 * 100 (不同設施的總數 * 設施的對應數量)
int dp[100000];
int main()
{
int n,i,j,a,b,l,sum;
while(~scanf("%d",&n),n>0)
{
memset(val,0,sizeof(val));
memset(dp,0,sizeof(dp));
l = 0;
sum = 0;
for(i = 0;i<n;i++)
{
cin>>a>>b;
while(b--)
{
val[l++] = a;
sum+=a;
}
}
for(i = 0;i<l;i++) //相當於物品個數
{
for(j = sum/2;j>=val[i];j--) //sum/2 相當於揹包容量
{
dp[j] = max(dp[j],dp[j-val[i]]+val[i]);
}
}
cout<<sum-dp[sum/2]<<' '<<dp[sum/2]<<endl; //前者一定大於後者
}
return 0;
}
做法:換一種思維,因爲題目要求要儘量平均分配,所以我們可以先將總價值 sum 求出,然後得出其分配的平均值爲 sum/2,這個值即揹包總容量,可以得出最小那個價值(因爲01揹包算出的最大值小於等於揹包容量),所以另一個值即最大,用 sum 減去即可
經典例題3
樣例輸入
1
5 10
1 2 3 4 5
5 4 3 2 1
樣例輸出
14
AC代碼
#include<iostream>
using namespace std;
struct Node
{
int val,vol;
}node[1005];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,v,sum=0,i,j;
cin>>n>>v;
int dp[1005]={0};
for(int i=1;i<=n;i++)
cin>>node[i].val;
for(int i=1;i<=n;i++)
cin>>node[i].vol;
for(i=1;i<=n;i++)
for(j=v;j>=node[i].vol;j--)
dp[j]=max(dp[i],dp[j-node[i].vol]+node[i].val);
cout<<dp[v]<<endl;
}
return 0;
}
01揹包的原型題,只是用一個結構體替換了兩個數組
經典例題4 (很難)
樣例輸入
3
5 10 2
1 2 3 4 5
5 4 3 2 1
5 10 12
1 2 3 4 5
5 4 3 2 1
5 10 16
1 2 3 4 5
5 4 3 2 1
樣例輸出
12
2
0
AC代碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct Node
{
int price;
int val;
} node[1005];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,v,k,i,dp[1005][31] = {0},a[31],b[31]; //dp[i][j]表示i體積下第j優解
scanf("%d%d%d",&n,&v,&k); //骨頭的數量,袋子的體積,第 K優解
for(i = 0; i<n; i++)
scanf("%d",&node[i].price);
for(i = 0; i<n; i++)
scanf("%d",&node[i].val);
int j;
for(i = 0; i<n; i++)
{
for(j = v; j>=node[i].val; j--)
{
int d;
for(d = 1; d<=k; d++) //分別將放入第 i個石頭與不放第 i個石頭的結果存入數組 a b 中
{
a[d] = dp[j-node[i].val][d]+node[i].price;
b[d] = dp[j][d];
}
int x,y,z;
x = y = z = 1;
a[d]=b[d]=-1; //此 d 相當於 k+1, 即越界
while(a[x]!=-1||b[y]!=-1) //產生分歧後找到其中最大的 k的保留
{
if(a[x] > b[y])
dp[j][z] = a[x++];
else
dp[j][z] = b[y++];
if(dp[j][z]!=dp[j][z-1]) //因爲優解的值不能相同, 去掉重複了的優解
z++;
}
}
}
printf("%d\n",dp[v][k]);
}
return 0;
}
分析:因爲要求第k大,故不能有重複的解
做法:將放或不放某一塊石頭分別存入數組中,最後歸併從小到大排序進 dp 中,這樣就可以存儲特定體積下第 k 優解(最大價值),最後輸出即可
經典例題5(難)
樣例輸入
3
0.04 3
1 0.02
2 0.03
3 0.05
0.06 3
2 0.03
2 0.03
3 0.05
0.10 3
1 0.03
2 0.02
3 0.05
樣本輸出
2
4
6
AC代碼
#include<iostream>
#include<cstring>
using namespace std;
int money[110]; //表示每個銀行的錢(相當於揹包容量)
double pp[110]; //被抓的概率
double dp[110*110];
int main()
{
int t;
double p; //表示最大允許的概率
int n; //計劃的銀行數
cin>>t;
while(t--)
{
memset(dp,0,sizeof(dp));
int sum=0;
cin>>p>>n;
for(int i=1;i<=n;i++)
{
cin>>money[i]>>pp[i];
sum+=money[i];
}
dp[0]=1; //以概率 1 爲初始值
for(int i=1;i<=n;i++)
{
for(int j=sum;j>=money[i];j--)
{
dp[j]=max(dp[j],(dp[j-money[i]])*(1-pp[i]));
}
}
int ans=0;
for(int j=0;j<=sum;j++) //包括一個銀行都不能搶的情況
{
if((1-dp[j]-p)<1e-8) //卡一點精度,由於是double類型
ans=max(ans,j);
}
cout<<ans;
}
return 0;
}
題目大意:先給出幾組數據,每組數據第一行是被抓的總概率 p (最後求得的總概率必須小於它,否則被抓),然後是想搶的銀行數 n 行,每行分別是該銀行能搶的錢數 m [ i ] 和被抓的概率 p [ i ],被抓的概率越大,逃跑概率越小,求最大逃跑概率
做法:揹包容量必然是錢數,然後是求最大逃跑概率,而題中每組數據第一行是被抓概率,所以要先用 1 減一下,還有最後求得的逃跑概率是隨着搶銀行的數量增加而減少,多搶一個銀行,其錢數必將轉化爲概率的乘積,所以動態方程也要做出改變;最後遍歷,剩餘的錢數越多,說明所搶的錢數越少,逃跑機率越大,所以從大到小遍歷揹包容量,一旦大於 p,即爲最大概率則跳出
經典例題6(難)
樣例輸入
2 10
10 15 10
5 10 5
3 10
5 10 5
3 5 6
2 7 3
樣例輸出
5
11
AC代碼
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
struct node
{
int p,q,v;
}a[505];
int cmp(node x,node y)//按 q-p排序,保證差額最小爲最優
{
return x.q-x.p<y.q-y.p;
}
int main()
{
int n,m,i,j;
int dp[5005]={0};
while(~scanf("%d%d",&n,&m))
{
for(i = 0; i<n; i++)
cin>>a[i].p>>a[i].q>>a[i].v;
memset(dp,0,sizeof(dp));
sort(a,a+n,cmp);
for(i = 0; i<n; i++)
{
for(j = m; j>=a[i].q; j--) //剩餘的錢大於 q 才能買
{
dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v); //減去 p
}
}
cout<<dp[m];
}
return 0;
}
分析:dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v)
表達的是這第 i 個物品要不要買,但同時也在判斷第 i 個物品要不要先買,如果先買剩下的空間隨便放,看是否得到的價值會更大
做法:將物品按差值(q - p)進行排序,小的排在前面,然後就用01揹包計算最高價值
經典例題7
樣例輸入
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
樣例輸出
123.50
1000.00
1200.50
AC代碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[3000005]; //由於每張發票不超過 1000,最多 30 張,擴大 100 倍數後開這麼大即可
int main()
{
char ch;
double x,y;
int sum,a,b,c,money[35],v;
int t,i,j,k;
while(~scanf("%lf%d",&x,&t),t)
{
sum = (int)(x*100);//將小數化作整數處理
memset(money,0,sizeof(money));
memset(dp,0,sizeof(dp));
int l = 0;
for(i = 0; i<t; i++)
{
cin>>k;
a = b = c = 0;
int flag = 1;
while(k--)
{
scanf(" %c:%lf",&ch,&y);
v = (int)(y*100);
if(ch == 'A' && a+v<=60000)
a+=v;
else if(ch == 'B' && b+v<=60000)
b+=v;
else if(ch == 'C' && c+v<=60000)
c+=v;
else
flag = 0;
}
if(a+b+c<=100000 && a<=60000 && b<=60000 && c<=60000 && flag) //注意 flag
money[l++] = a+b+c; //僅添加有效發票
}
for(i = 0; i<=l; i++)
{
for(j = sum; j>=money[i]; j--)
dp[j] = max(dp[j],dp[j-money[i]]+money[i]);
}
printf("%.2lf\n",dp[sum]/100.0);
}
return 0;
}
分析:
1、只有 a,b,c 三種物品可以報銷,含有其他物品的發票作廢
2、每個物品的價值不能超過 600
3、每張發票總價值不能超過 1000
做法:
先判斷哪些發票可以報銷,添加進 money 數組中,再對數組進行01揹包求出最大報銷金額
經典例題8(非常難)
題目描述
DD 和好朋友們要去爬山啦!他們一共有 K 個人,每個人都會背一個包,每個人包的容量是相同的,都是 V
可以裝進揹包裏的一共有 N 種物品,每種物品都有給定的體積和價值。
在 DD 看來,合理的揹包安排方案是這樣的:
1、每個人揹包裏裝的物品的總體積恰等於包的容量
2、每個包裏的每種物品最多隻有一件,但兩個不同的包中可以存在相同的物品
3、任意兩個人,他們包裏的物品清單不能完全相同
在滿足以上要求的前提下,所有包裏的所有物品的總價值最大是多少呢?
輸入格式
第一行有三個整數:K,V,N ( k<=50 v<=5000 n<=200)
第二行開始的 N 行,每行有兩個整數,分別代表這件物品的體積和價值
輸出格式
只需輸出一個整數,即在滿足以上要求的前提下所有物品的總價值的最大值
樣例輸入
2 10 5
3 12
7 20
2 4
5 6
1 1
樣例輸出
57
AC代碼
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n, v, k, c[205], w[205], q[55], ans = 0;
int f[5005][55]; //揹包容量 和 第 k優解
int main()
{
cin>>k>>v>>n; //人數 揹包容量 物品數
memset(f, 128, sizeof(f)); f[0][1] = 0; //數組 f初始化越大越好且將f[0][1]初始化爲 0
for(int i=1;i<=n;i++) cin >> c[i] >> w[i];
for(int i=1;i<=n;i++)
for(int j=v;j>=c[i];j--)
{
int now = 1, last = 1, cnt = 0;
while(cnt < k) //不用去重(只要滿足不同物品即可)
{
if(f[j][now] > f[j-c[i]][last]+w[i])
q[++cnt] = f[j][now++];
else q[++cnt] = f[j-c[i]][last++]+w[i];
}
for(int z=1;z<=k;z++) f[j][z] = q[z];
}
for(int i=1;i<=k;i++) ans += f[v][i];
cout<<ans;
return 0;
}
分析:與經典例題4 類似,但是求前 k 個最優解,所以不用去重
做法:整體與經典例題4 類似,卻採用了數組 f 初始化越大越好 且 將f[0][1]
初始化爲 0 的方法,沒有記錄放或不放物品的特解,而是直接將特解進行比較,從小到大存儲在新的數組中,再最後遍歷一遍將值重新賦回 (目前暫未掌握,有待理解)