01背包例题:
题目:传送门
/*给你n种不同的物品,每个物品有自己的体积w[i]和价值v[i],如果每个物品只能拿一次,给你容量为m的背包,怎样才能获取最大价值;
主线:使容量为m的背包装入的物品价值最大;
dp[j] 记录当容量为j时的可行取法的最大价值
状态转移方程:dp[j]=max( dp[j], dp[j-w[i]] +v[i]] );
*/
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e3+5; 4 int w[maxn],v[maxn],dp[maxn]; 5 int main() 6 { 7 int t; 8 scanf("%d",&t); 9 while(t--) 10 { 11 int n,m; 12 scanf("%d%d",&n,&m); 13 for(int i=0;i<n;i++) 14 scanf("%d",&v[i]); 15 for(int i=0;i<n;i++) 16 scanf("%d",&w[i]); 17 memset(dp,0,sizeof(dp)); 18 for(int i=0;i<n;i++) 19 { 20 for(int j=m;j>=w[i];j--) //因为每一个物品只能使用一次,所以为了防止dp[j-w[i]]中也使用了第i个物品,我们就应该倒序循环 21 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 22 } 23 printf("%d\n",dp[m]); 24 } 25 }
完全背包:
题目:传送门
刚开始m钱 一共years年 d多个基金,第i个股票需要 花费cost[i],收益w[i]。问最大能收益多少?
每一年是一个多重背包问题,因为每一个股票可以购买多次。。那么一共years个背包问题。
卡就卡在开多大数组。因为开不了那么大的数组。。所以,因为基金都是1000的倍数,所以把m和基金的花费都缩小1000去进行背包求解,
得到的解用未缩小的总钱数去加,然后再缩小1000去求解下一年的。
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define MAX 1000100 5 using namespace std; 6 long long dp[MAX]; 7 int v[1000]; 8 int w[1000]; 9 int main() 10 { 11 long long n,d,i,j,money,year,mon,ww,vv; 12 scanf("%lld",&n); 13 while(n--) 14 { 15 memset(v,0,sizeof(v)); 16 memset(w,0,sizeof(w)); 17 scanf("%lld%lld",&money,&year); 18 scanf("%lld",&d); 19 for(i=1;i<=d;i++) 20 { 21 scanf("%lld%lld",&ww,&v[i]); 22 w[i]=ww/1000; 23 } 24 memset(dp,0,sizeof(dp)); 25 while(year--) //因为每一年你所拥有的钱都不一样,所以最优解也会改变 26 { 27 mon=money/1000; 28 for(i=1;i<=d;i++) //d种股票 29 for(j=w[i];j<=mon;j++) //因为每种股票可以使用多次,所以我们就要正着来循环,因为这样的话我们用到的dp[j-w[i]]肯定已经 30 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); //找到了最优解,这个最优解可能使用过了第i个股票,所以就满足题意 31 money+=dp[j-1]; //最后一个就是答案 32 } 33 printf("%lld\n",money); 34 } 35 return 0; 36 }
多重背包:
题目:传送门
Input 输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。 Output 对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。 Sample Input 1 8 2 2 100 4 4 100 2 Sample Output 400
题解:
使用多重背包解决物品有限问题
每一种物品都有个数,这个时候我们可以首先把他们用二进制分开,然后再用01背包方法找最优解。
这里解释一下怎么把他们用二进制分开:比如一个物品有10个,那么就可以把这个物品分成1、2、4、8,分别把这一种物品得1个在一起,2个在一起,4个在一起......当成一个新的物品,而且每个物品只有一个,最后剩下的也为一个物品。让它转化成这样后就可以使用01背包来做
为什么可以这样做:因为你的最优解中每一个物品用的次数都可以用1、2、4、8....来组成,他们可以组成1—n中所有的数
注释+二进制优化代码:
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 5 class node{ 6 public: 7 int p, h, c; 8 }rice[105]; 9 int n, m; 10 int dp[105]; 11 12 int main(){ 13 14 int C; 15 cin >> C; 16 while( C-- ){ 17 memset( dp, 0, sizeof( dp ) ); 18 19 cin >> n >> m; 20 21 for( int i = 1; i <= m; i++ ) 22 cin >> rice[i].p >> rice[i].h >> rice[i].c; 23 24 for( int i = 1; i <= m; i++ ){ //枚举物品 25 int p = 1; 26 while( p < rice[i].c ){ //把物品二进制分开 27 for( int j = n; j >= rice[i].p * p; j-- ) 28 if( dp[j] < dp[j - rice[i].p * p] + rice[i].h * p) 29 dp[j] = dp[j - rice[i].p * p] + rice[i].h * p; 30 31 rice[i].c -= p; 32 p <<= 1; //相当于乘与2 33 } 34 35 for( int j = n; j >= rice[i].p * rice[i].c; j-- ) //01背包格式 36 if( dp[j] < dp[j - rice[i].p * rice[i].c] + rice[i].h * rice[i].c ) 37 dp[j] = dp[j - rice[i].p * rice[i].c] + rice[i].h * rice[i].c; 38 } 39 40 cout << dp[n] << endl; 41 } 42 return 0; 43 }
没有二进制代码:
1 #include<stdio.h> 2 #include<string.h> 3 int max(int x,int y) 4 { 5 if(x>y) return x; 6 else return y; 7 } 8 int q[1105],v[105],w[105],e[105]; 9 int main() 10 { 11 int a,s,d,f,g,h; 12 scanf("%d",&a); 13 while(a--) 14 { 15 memset(v,0,sizeof(v)); 16 scanf("%d%d",&s,&d); 17 for(f=1;f<=d;++f) 18 { 19 scanf("%d%d%d",&q[f],&w[f],&e[f]); 20 } 21 for(f=1;f<=d;++f) //枚举物品 ,完全暴力dp 22 { 23 for(g=s;g>=q[f];--g) 24 { 25 for(h=1;h<=e[f];++h) 26 { 27 if(h*q[f]>g) break; 28 else 29 { 30 v[g]=max(v[g],v[g-h*q[f]]+h*w[f]); 31 } 32 } 33 } 34 } 35 printf("%d\n",v[s]); 36 } 37 return 0; 38 }
三种背包的混合:
就只需要判断一下是完全背包还是多重背包就可以了,加一个判断
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 int m,n,w[31],c[31],p[31],f[201]; 7 int main() 8 { 9 scanf("%d%d",&m,&n); 10 for(int i=1;i<=n;i++) 11 scanf("%d%d%d",&w[i],&c[i],&p[i]); 12 for(int i=1;i<=n;i++) 13 if(p[i]==0) //完全背包 14 { 15 for(int j=w[i];j<=m;j++) 16 f[j]=max(f[j],f[j-w[i]]+c[i]); 17 } 18 else //多重背包 19 { 20 for(int j=1;j<p[i];j++) //这一层for循环还可以用二进制来优化 21 for(int k=m;k>=w[i];k--) 22 f[k]=max(f[k],f[k-w[i]]+c[i]); 23 } 24 printf("%d",f[m]); 25 return 0; 26 }
二维费用背包:
例题:(ssl 2290)潜水员
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 const int MAXN=1001; 5 const int MAXM=101; 6 const int INF=0x3f3f3f3f; 7 using namespace std; 8 int v,u,k; 9 int a[MAXN],b[MAXN],c[MAXN]; 10 int f[MAXM][MAXM]; 11 int main() 12 { 13 memset(f,INF,sizeof(f)); 14 printf("%d\n",f[1][1]); 15 f[0][0]=0; 16 scanf("%d%d%d",&v,&u,&k); 17 for(int i=1;i<=k;i++) 18 scanf("%d%d%d",&a[i],&b[i],&c[i]); 19 for(int i=1;i<=k;i++) //枚举罐子 20 for(int j=v;j>=0;j--) //枚举氧气容量 21 for(int l=u;l>=0;l--) //枚举氮气容量 22 { 23 int t1=j+a[i],t2=l+b[i]; 24 if(t1>v) t1=v; 25 if(t2>u) t2=u; 26 if(f[t1][t2]>f[j][l]+c[i]) f[t1][t2]=f[j][l]+c[i]; 27 } 28 printf("%d",f[v][u]); 29 return 0; 30 }