背包入门练习
POJ3624Charm Bracelet(01背包)
01背包看看《背包九讲》第一讲就没问题。http://wenku.baidu.com/view/e5fd2015b7360b4c2e3f6416.html
View Code
1 #include<stdio.h> 2 int d[3405],w[3405]; 3 int n,m; 4 int ZeroOnePack() //01背包解法 5 { 6 int f[13000]={0},i,j; 7 for( i=1; i<=n ; i++ ) 8 { 9 for( j=m ; j>=w[i]; j-- ) 10 { 11 if(f[j]<f[j-w[i]]+d[i] ) //看是否更优 12 f[j]=f[j-w[i]]+d[i]; 13 } 14 } 15 return f[m]; 16 } 17 int main() 18 { 19 while(~scanf("%d%d",&n,&m)) 20 { 21 int i; 22 for(i=1;i<=n;i++)scanf("%d%d",&w[i],&d[i]); 23 printf("%d\n",ZeroOnePack()); 24 } 25 return 0; 26 }
HDU1114Piggy-Bank(完全背包)
题目大意:有一个pig-bank(其实就是一个存钱罐),已知它的大小(由空罐子和装满后可算出)v,有n种钱币,每种价值是p,大小是w,问当刚好装满时最少有多少钱。
这个就是一个很明显的简单完全背包,只是和平时不同的是,他是求最小价值而不是最大。而且背包不是随意装而是要装满。
完全背包的概念可以借鉴上面的网址,这里面全部都有介绍。
其中最重要的就是状态转移方程
f[j]=f[j-cost]+weight;
这里求最小价值,所以要更改的就是,初始两赋值为正无穷而不是负无穷(f[0]=0),比较时返回较小值。代码:
View Code
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<queue> 5 #include<string> 6 #include<stack> 7 #include<cmath> 8 #include<cctype> 9 #include<iostream> 10 #include<set> 11 #include<algorithm> 12 #include<ctime> 13 #include<vector> 14 using namespace std; 15 #define mem(a) memset(a,0,sizeof(a)) 16 const int MAXN=2000000007; 17 int dp[10000],c[505],w[505]; 18 int v,n; 19 int min(int x,int y)//返回较小的值 20 { 21 return x<y?x:y; 22 } 23 int complete_pack()//完全背包 24 { 25 int i,j; 26 for(i=0;i<n;i++) 27 for(j=c[i];j<=v;j++)//c。。。v 28 dp[j]=min(dp[j],dp[j-c[i]]+w[i]);//状态转移方程 29 return dp[v]==MAXN?0:dp[v]; 30 } 31 int main() 32 { 33 int cas; 34 scanf("%d",&cas); 35 while(cas--) 36 { 37 int x,y;mem(c);mem(w); 38 scanf("%d%d",&x,&y); 39 v=y-x; 40 scanf("%d",&n); 41 for(x=0;x<n;x++)scanf("%d%d",&w[x],&c[x]); 42 for(x=1;x<=v;x++)dp[x]=MAXN;//全部是不满足式,赋值为无穷大 43 //如果是秋最大价值,赋值为负无穷大 44 dp[0]=0;// 只有一个赋值为0 45 int result=complete_pack(); 46 if(result)printf("The minimum amount of money in the piggy-bank is %d.\n",result); 47 else printf("This is impossible.\n"); 48 } 49 return 0; 50 }
HDU4508湫湫系列故事——减肥记I(完全背包)
和上面一样的。给代码:
View Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define mem(a) memset(a,0,sizeof(a)) 5 using namespace std; 6 int max(int a,int b) 7 { 8 return a>b?a:b; 9 } 10 int main() 11 { 12 int f[100005],n,c[105],w[105]; 13 while(~scanf("%d",&n)) 14 { 15 int j,i,v;mem(f); 16 for(i=0;i<n;i++)scanf("%d%d",&w[i],&c[i]); 17 scanf("%d",&v); 18 for(i=0;i<n;i++) 19 { 20 for(j=c[i];j<=v;j++)f[j]=max(f[j-c[i]]+w[i],f[j]); 21 } 22 printf("%d\n",f[v]); 23 } 24 return 0; 25 }
HDU1171 Big Event in HDU(多重背包)
题目大意:有n种设施,每种的价值是v,数目是m,题目要求双方所分得的价值尽可能的接近。
最初一看上去还不觉得似乎是背包问题,可是一分析不难明白,设所有物品总价值是sum,那么每一方的价值就不会超过half=sum/2.这样就转化为了背包大小为half,每个物品大小和价值都是v的多重背包问题。即:
花费(cost=v; 价值(wealth)= v; 数目(num)=m
时间复杂度是O(half*(n个m[i]的和))
然后就是二进制优化,复杂度可减少到O(half*(log(n个m[i]的和))),大大降低。
实现方法: for(对每一个物品)
if(cost*num〉=V)完全背包
else
k=1;
while k<num
01背包(k*cost,k*weight);
num-=k;
k<<1;
01背包(num*cost,num*weight)
给出代码:
View Code
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<queue> 5 #include<string> 6 #include<stack> 7 #include<cmath> 8 #include<cctype> 9 #include<iostream> 10 #include<set> 11 #include<algorithm> 12 #include<ctime> 13 #include<vector> 14 using namespace std; 15 #define mem(a) memset(a,0,sizeof(a)) 16 #define MAXN 1000000007 17 int v[1005],m[1005],n; 18 int f[1500000]; 19 int sum,half; 20 int max(int a,int b) 21 { 22 return a>b?a:b; 23 } 24 void Zero_One_pack(int vv)//01背包求解 25 { 26 int i; 27 for(i=half;i>=vv;i--) 28 f[i]=max(f[i],f[i-vv]+vv); 29 } 30 void complete_pack(int vv)//完全背包求解 31 { 32 int i; 33 for(i=vv;i<=half;i++) 34 f[i]=max(f[i],f[i-vv]+vv); 35 } 36 int pack()//混合背包求解 37 { 38 int i;mem(f); 39 for(i=0;i<n;i++) 40 { 41 if(m[i]*v[i]>=half){//完全背包 42 complete_pack(v[i]); 43 continue; 44 } 45 int k=1; 46 while(k<m[i])//所谓的二进制模拟 47 { 48 Zero_One_pack(k*v[i]); 49 m[i]-=k; 50 k*=2; 51 } 52 Zero_One_pack(m[i]*v[i]); 53 } 54 return f[half]; 55 } 56 int main() 57 { 58 int i; 59 while(~scanf("%d",&n)&&n>=0) 60 { 61 if(n==0){printf("0 0\n");continue;} 62 sum=0; 63 for(i=0;i<n;i++){ 64 scanf("%d%d",&v[i],&m[i]); 65 sum+=v[i]*m[i]; 66 } 67 half=sum/2; 68 int a=pack(); 69 printf("%d %d\n",sum-a,a); 70 } 71 return 0; 72 }
HDU2844Coins(多重)
这里有一段代码,思路是copy下来的,但是有一段不太懂。
View Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define mem(a) memset(a,0,sizeof(a)) 5 using namespace std; 6 int f[100005],c[105],w[105],n,m; 7 int max(int a,int b) 8 { 9 return a>b?a:b; 10 } 11 void z_o_pack(int v) 12 { 13 for(int i=m;i>=v;i--) 14 f[i]=max(f[i],f[i-v]+v); 15 } 16 void com_pack(int v) 17 { 18 for(int i=v;i<=m;i++) 19 f[i]=max(f[i],f[i-v]+v); 20 } 21 void pack() 22 { 23 for(int i=0;i<n;i++) 24 { 25 if(c[i]*w[i]>=m){com_pack(c[i]);continue;} 26 int k=1; 27 while(k<w[i]) 28 { 29 z_o_pack(k*c[i]); 30 w[i]-=k; 31 k*=2; 32 } 33 z_o_pack(w[i]*c[i]); 34 } 35 } 36 int main() 37 { 38 while(~scanf("%d%d",&n,&m)) 39 { 40 if(!n&&!m)break; 41 int i; 42 for(i=0;i<n;i++)scanf("%d",&c[i]); 43 for(i=0;i<n;i++)scanf("%d",&w[i]); 44 mem(f); 45 pack(); 46 int count=0; 47 for(i=1;i<=m;i++){ 48 if(f[i]==i)count++;//这里是为什么??? 49 } 50 printf("%d\n",count); 51 } 52 return 0; 53 }
常规解法:在放进包里面时,每放一个,记录一个数据,这样就可以保证不遗漏任何一种可能。代码(转自http://blog.csdn.net/new_c_yuer/article/details/6670350):
View Code
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<utility> 5 #include<string> 6 #include<set> 7 #include<vector> 8 #include<stack> 9 #include<algorithm> 10 #include<queue> 11 #include<cstdlib> 12 #include<map> 13 #include<cmath> 14 using namespace std; 15 const int M=100010; 16 const int inf=1<<29; 17 int cout; 18 bool bag[M]; 19 int used[M],c[M],v[M]; 20 int n,m; 21 22 void work() 23 { 24 int i,j; 25 memset(bag,0,sizeof(bag)); 26 bag[0]=1; 27 for(i=1;i<=n;i++) 28 { 29 memset(used,0,sizeof(used)); 30 for(j=v[i];j<=m;j++) 31 if(!bag[j] && bag[j-v[i]] && used[j-v[i]]<c[i]) 32 { 33 bag[j]=true; 34 used[j]=used[j-v[i]]+1; 35 cout++; 36 } 37 } 38 } 39 40 int main() 41 { 42 while(scanf("%d%d",&n,&m)==2) 43 { 44 if(n+m==0) break; 45 int i,j; 46 cout=0; 47 for(i=1;i<=n;i++) 48 scanf("%d",&v[i]); 49 for(i=1;i<=n;i++) 50 scanf("%d",&c[i]); 51 work(); 52 printf("%d\n",cout); 53 } 54 return 0; 55 }
HDU1712ACboy needs your help(分组背包)
首先很容易看出它是01背包的扩展,其次根据上面的思路,由于一组中只能选择一个,所以对每一个组找出最优解,状态转移方程:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]物品i属于第k组}
伪代码就是: for 所有的组k
for v=V...0
for 所有的i属于k组
f[v]=max{f[v],f[v-c[i]]+w[i]}
View Code
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<queue> 5 #include<string> 6 #include<stack> 7 #include<cmath> 8 #include<cctype> 9 #include<iostream> 10 #include<set> 11 #include<algorithm> 12 #include<ctime> 13 #include<vector> 14 #define mem(a) memset(a,0,sizeof(a)) 15 #define MAXN 1000000007 16 int f[105],w[105][105]; 17 int n,m; 18 int max(int a,int b) 19 { 20 return a>b?a:b; 21 } 22 void Zero_One_pack(int k)//第k组 23 { 24 for(int i=m;i>=1;i--)//01背包 25 for(int j=1;j<=m&&i>=j;j++)//它们都属于组k 26 f[i]=max(f[i],f[i-j]+w[k][j-1]); 27 } 28 int group_pack()//分组背包 29 { 30 mem(f); 31 for(int i=0;i<n;i++) 32 Zero_One_pack(i) ; 33 return f[m]; 34 } 35 int main() 36 { 37 while(~scanf("%d%d",&n,&m)) 38 { 39 if(!m&&!n)break; 40 for(int i=0;i<n;i++) 41 for(int j=0;j<m;j++) 42 scanf("%d",&w[i][j]); 43 printf("%d\n",group_pack()); 44 } 45 return 0; 46 }
HDU3535AreYouBusy
最后加上一道综合性比较强的背包问题(对于入门选手来说,比如我==|,会很难做),他是一道分组了的混合三种背包的问题。就连我自己也很难做出来,我也是参考的其他大神的博客http://blog.csdn.net/x314542916/article/details/8723233,代码放在下面,希望读者好好体会每条语句的含义:(只能说自己太弱菜。。。)
View Code
1 /* 2 http://acm.hdu.edu.cn/showproblem.php?pid=3535 AreYouBusy 3 混合背包,还分组了 ,三种类型的set 最多取一个,最少取一个和没有限制 4 */ 5 6 #include <cstdio> 7 #include <iostream> 8 #include <string> 9 #include <cstring> 10 #define CLR(c,v) (memset(c,v,sizeof(c))) 11 using namespace std; 12 13 const int inf = -(1<<30); 14 const int INF = (1<<30); 15 const int M = 1e2 + 10; 16 17 template <typename _T> 18 _T Max(_T a , _T b){ 19 return (a>b)?(a):(b); 20 } 21 template <typename _T> 22 _T Max(_T a , _T b , _T c){ 23 return (a>Max(b,c))?(a):(Max(b,c)); 24 } 25 26 int dp[M][M]; 27 int v[M]; // :value 28 int c[M]; // :cost 29 30 int main(){ 31 //freopen("in.txt","r",stdin); 32 int n_set, max_cost; 33 while(cin >> n_set >> max_cost){ 34 CLR(dp,0); 35 for (int i = 1 ; i <= n_set ; i++){ 36 //0 stands for the sets that should choose at least 1 job to do, 37 //1 for the sets that should choose at most 1 , 38 //2 for the one you can choose freely 39 int n_case, case_type; 40 cin >> n_case >> case_type ; 41 for(int j = 1 ; j <= n_case ; j++){ 42 cin >> c[j] >> v[j]; 43 } 44 if(case_type == 0){ 45 for (int k = 0 ; k <= max_cost ; k++) // 这样确保最少放一个物品 46 dp[i][k] = inf; 47 for (int j = 1 ; j <= n_case ; j++) 48 for (int k = max_cost ; k >= c[j] ; k--){ 49 //** 如果是第一次选,则一定能加入数组中 50 //** 如果不是第一次选,则当做普通01背包处理 51 dp[i][k] = Max(dp[i][k] , dp[i-1][k-c[j]] + v[j] , dp[i][k-c[j]] + v[j]); 52 } 53 }else if(case_type == 1){ 54 for (int k = 0 ; k <= max_cost ; k++) 55 dp[i][k] = dp[i-1][k]; // 为了保证全局最优解,初始化为上一次结果 56 for (int j = 1 ; j <= n_case ; j++) 57 for (int k = max_cost ; k >= c[j] ; k--){ 58 dp[i][k] = Max(dp[i][k], dp[i-1][k-c[j]] + v[j]); 59 } 60 }else{ 61 for (int k = 0 ; k <= max_cost ; k++) 62 dp[i][k] = dp[i-1][k]; 63 for (int j = 1 ; j <= n_case ; j++) 64 for (int k = max_cost ; k >= c[j] ; k--){ 65 dp[i][k] = Max(dp[i][k], dp[i-1][k-c[j]] + v[j], dp[i][k-c[j]] + v[j]); 66 } 67 } 68 69 } 70 int res = Max(dp[n_set][max_cost] , -1); 71 cout << res << endl; 72 } 73 return 0; 74 }
背包问题实在是可以涉及太多,但实际比赛时能用的却很少,绝大多数都会与其他问题联合起来考察,或是题意隐含较深,所以还希望读者另外多加练习,才能随心所欲。当然,自己也还有很长的路要走。。。


浙公网安备 33010602011771号