首先01背包题目的雏形是
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
从这个题目中可以看出,01背包的特点就是:每种物品仅有一件,可以选择放或不放。
其状态转移方程是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
对于这方方程其实并不难理解,方程之中,现在需要放置的是第i件物品,这件物品的体积是c[i],价值是w[i],因此f[i-1][v]代表的就是不将这件物品放入背包,而f[i-1][v-c[i]]+w[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。
理解了这个方程后,将方程代入实际题目的应用之中,可得
for(i = 1; i<=n; i++) { for(j = v; j>=c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了 { f[i][v]=max(f[i-1][v],f[i-1][j-c[i]]+w[i]); } }
hdu2546
01背包,先对菜进行从小到大排序。
用m-5对前n-1个菜进行01背包,最后减去最贵的菜即可
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 1010 int n,m; int dp[maxn],v[maxn]; int main() { while(~scanf("%d",&n)) { if(n==0)break; for(int i=1;i<=n;i++)scanf("%d",&v[i]); sort(v+1,v+n+1); scanf("%d",&m); if(m<5) { printf("%d\n",m); continue; } memset(dp,0,sizeof(dp)); m-=5; for(int i=1;i<n;i++) { for(int j=m;j>=v[i];j--)//01pack { dp[j]=max(dp[j],dp[j-v[i]]+v[i]); } } printf("%d\n",m+5-dp[m]-v[n]); } return 0; }
hdu1171
要求将物品尽量按照价值等分成两份,先对物品求和sum
对sum/2进行01背包
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int v[5500]; int dp[250050]; int sum,cnt; int main() { int n; while(~scanf("%d",&n)&&n>0) { sum=0,cnt=0; for(int i=0;i<n;i++){ int a,b; scanf("%d%d",&a,&b); while(b--) { v[cnt++]=a; sum+=a; } } memset(dp,0,sizeof(dp)); for(int i=0;i<cnt;i++) { for(int j=sum/2;j>=v[i];j--) { dp[j]=max(dp[j],dp[j-v[i]]+v[i]); } } printf("%d %d\n",sum-dp[sum/2],dp[sum/2]); } return 0; }
hdu2602
给你n个物体的价值和体积,背包的体积,求最大价值
裸01背包
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int v[1100],c[1100]; int dp[1100]; int main() { int T; scanf("%d",&T); int n,m; while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++)scanf("%d",&v[i]); for(int i=0;i<n;i++)scanf("%d",&c[i]); memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) { for(int j=m;j>=c[i];j--) { dp[j]=max(dp[j],dp[j-c[i]]+v[i]); } } printf("%d\n",dp[m]); } }
hdu2639
01背包第k优解
首先dp[i][j]代表的是在体积为i的时候第j优解为dp[i][j]......那么,我们就可以这样思考,i对应体积,那么如果只是一维的dp[i],代表的应该是体积为i时的最大值,那么同理,dp[i][1]代表的是体积为i时的最大值,那么我们就可以推出两种状态,dp[i][d],dp[i-c[i]][d]+v[i].....然后把这两种状态开个两个数组分别保存起来,再合并出体积为i时的前k优解......依次后推,直到dp[m][k].......
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; int v[1100],c[1100]; int dp[1100][50]; int a[50],b[50]; bool cmp(int a,int b) { return a>b; } int main() { int T; scanf("%d",&T); int n,m,k; while(T--) { scanf("%d%d%d",&n,&m,&k); for(int i=0;i<n;i++)scanf("%d",&v[i]); for(int i=0;i<n;i++)scanf("%d",&c[i]); memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) { for(int j=m;j>=c[i];j--) { for(int d=1;d<=k;d++) { a[d]=dp[j-c[i]][d]+v[i]; b[d]=dp[j][d]; } int x=1,y=1,z=1; a[k+1]=b[k+1]=-1;//一旦x或者y超过最k后要给x,y赋值最小值,以免误判 while(z<=k&&(x<=k||y<=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[m][k]); } }
hdu2955
01背包,不被逮捕的概率作为物品的价值,dp[i]表示得到i元物品时不被逮捕的概率的最大值,最后从sum往前遍历,找到的第一个即是答案
注意初始化,01背包乘法时dp[0]=1.0 ,dp[1-sum]=0
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; int c[110]; double dp[10010],v[110]; int main() { int T; scanf("%d",&T); int n; double p; while(T--) { dp[0]=1.0; for(int i=1;i<10010;i++)dp[i]=0; int sum=0; scanf("%lf%d",&p,&n); p=1-p; for(int i=0;i<n;i++) { scanf("%d%lf",&c[i],&v[i]); v[i]=1-v[i]; sum+=c[i]; } for(int i=0;i<n;i++) { for(int j=sum;j>=c[i];j--) { dp[j]=max(dp[j],dp[j-c[i]]*v[i]); } } for(int i=sum;i>=0;i--) { if(dp[i]>p) { printf("%d\n",i); break; } } } }
hdu3466
比如对 3 5 6,5 10 5这两个物品,如果我们的决策是两个都不选或者是只选其中一个,显然没什么问题,但如果我们要是两个都选的话,按照之前这个顺序有m>=13,但如果把两个的顺序交换一下则m>=10即可,从这里就可以看出问题的所在了。于是,对任意两个物品i,j,为了避免上面存在的那种问题,我们可以算出两种顺序所需要的最少金额,若i->j,则至少需要Pi+Qj,若是j->i则至少需要Pj+Qi。如果已知结果是i->j较优的话,则有Pi+Qj<Pj+Qi,即Qi-Pi>Qj-Pj。所以若对之前的物品先按照Q-P由大到小排好序后
然后就是普通的01背包
写的时候是q-p从小到大排,讲不清原因..留坑
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct ss { int p,q,v; }a[550]; int dp[500050]; bool cmp(ss a,ss b) { return a.p+b.q>a.q+b.p; } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].v); sort(a,a+n,cmp); for(int i=0;i<n;i++) { for(int j=m;j>=a[i].q;j--)//a[i].q>=a[i].p { dp[j]=max(dp[j],dp[j-a[i].p]+a[i].v); } } printf("%d\n",dp[m]); } return 0; }
hdu1864
选出可以报销的发票进行01背包即可
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define maxn 3000030 int n,m; int v[50]; int dp[maxn]; int main() { double tmp; while(~scanf("%lf%d",&tmp,&n)&&n) { int cnt=0; m=tmp*100; memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++){ int k; int a[3]={0}; scanf("%d",&k); int flag=1; while(k--) { char ch[20]; scanf("%s",ch); sscanf(ch+2,"%lf",&tmp); int t=tmp*100; if(ch[0]=='A'||ch[0]=='B'||ch[0]=='C'){ a[ch[0]-'A']+=t; if(a[ch[0]-'A']>60000) flag=0; } else flag=0; } if(a[0]+a[1]+a[2]<=100000&&flag) { v[cnt++]=a[0]+a[1]+a[2]; } } for(int i=0;i<cnt;i++) { for(int j=m;j>=v[i];j--) { dp[j]=max(dp[j],dp[j-v[i]]+v[i]); } } printf("%.2lf\n",dp[m]*1.0/100); } return 0; }