0/1背包 HDU2602
01背包(ZeroOnePack): 有N件物品和一个容量为V的背包,每种物品均只有一件。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即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]标签前一状态的价值?
逆序
这就是关键!
123 | 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]是前一状态的!这里给大家一组测试数据: 测试数据: 10,3 3,4 4,5 5,6
图2: 01背包图(1)
这个图表画得很好,借此来分析:
C[v]从物品i=1开始,循环到物品3,期间,每次逆序得到容量v在前i件物品时可以得到的最大值。
Bone Collector
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Followed by T cases , each case 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 the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 int main() 5 { 6 int t; 7 int pack, maxv; 8 int weight[1005], value[1005]; 9 int record[1005]; 10 scanf("%d", &t); 11 while (t--) 12 { 13 memset(record, 0, sizeof(record)); 14 scanf("%d %d", &pack, &maxv); 15 for (int i = 0; i < pack; i++) 16 scanf("%d", &value[i]); 17 for (int i = 0; i < pack; i++) 18 scanf("%d", &weight[i]); 19 for(int i=0;i<pack;i++) 20 for (int j = maxv; j >= weight[i]; --j) 21 { 22 if (record[j - weight[i]] + value[i] > record[j]) 23 record[j] = record[j - weight[i]] + value[i]; 24 } 25 printf("%d ", record[maxv]); 26 } 27 return 0; 28 }
完全背包 HDU 1248
寒冰王座
死亡骑士:"我要买道具!"
地精商人:"我们这里有三种道具,血瓶150块一个,魔法药200块一个,无敌药水350块一个."
死亡骑士:"好的,给我一个血瓶."
说完他掏出那张N元的大钞递给地精商人.
地精商人:"我忘了提醒你了,我们这里没有找客人钱的习惯的,多的钱我们都当小费收了的,嘿嘿."
死亡骑士:"......"
死亡骑士想,与其把钱当小费送个他还不如自己多买一点道具,反正以后都要买的,早点买了放在家里也好,但是要尽量少让他赚小费.
现在死亡骑士希望你能帮他计算一下,最少他要给地精商人多少小费.
注意:地精商店只有题中描述的三种道具.
900
250
1 //解法一:纯粹暴力 时间复杂度 O(n^3) n=100; 2 #include<stdio.h> 3 int main() 4 { 5 int t,n,i,j,k; 6 int price; 7 scanf("%d",&t); 8 while(t--) 9 { 10 int max=0; 11 scanf("%d",&n); 12 for(i=0;i<100;i++) 13 for(j=0;j<100;j++) 14 for(k=0;k<100;k++) 15 { 16 price=150*i+200*j+350*k; 17 if(price<=n&&price>=max) 18 max=price; 19 } 20 printf("%d ",n-max); 21 } 22 return 0; 23 } 24 25 //解法二:DP完全背包 26 #include<stdio.h> 27 #include<string.h> 28 int max(int a,int b) 29 { 30 return a>b?a:b; 31 } 32 int main() 33 { 34 int i,j,m,n; 35 int dp[10001]; 36 int a[3]={150,200,350}; 37 scanf("%d",&n); 38 while(n--) 39 { 40 scanf("%d",&m); 41 memset(dp,0,sizeof(dp)); 42 for(i=0;i<3;i++) 43 { 44 for(j=a[i];j<=m;j++) 45 { 46 dp[j]=max(dp[j],dp[j-a[i]]+a[i]); 47 } 48 } 49 printf("%d ",m-dp[m]); 50 } 51 return 0; 52 }
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
Input
1 #include<stdio.h> 2 #include<string.h> 3 int main() 4 { 5 int dp[150]; 6 int p[150],h[150],c[150]; 7 int C; 8 scanf("%d",&C); 9 while(C--) 10 { 11 memset(dp,0,sizeof(dp)); 12 int m,n; 13 scanf("%d %d",&n,&m);//两个整数n和m分别表示经费的金额和大米的种类 14 for(int i=0;i<m;i++) 15 scanf("%d %d %d",&p[i],&h[i],&c[i]);//每袋的价格、每袋的重量以及对应种类大米的袋数 16 for(int i=0;i<m;i++) 17 for(int j=0;j<c[i];j++) 18 for(int k=n;k>=p[i];--k) 19 if(dp[k]<dp[k-p[i]]+h[i]) 20 dp[k]=dp[k-p[i]]+h[i]; 21 printf("%d ",dp[n]); 22 } 23 return 0; 24 }