HDU_STEPS3.3 主要是背包问题
3.3.1 HDU1114 Piggy-Bank 完全背包,要正好装满
#include <cstdio> #include <string.h> using namespace std; int e,f,n; int d[10005]; int p[505],w[505]; int main(){ int cas; scanf("%d",&cas); while(cas--){ scanf("%d%d",&e,&f); e=f-e; scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&w[i]); //DP for(int i=1;i<=e;i++)d[i]=1e9; d[0]=0; for(int i=1;i<=n;i++){ for(int j=w[i];j<=e;j++){ if(d[j-w[i]]+p[i]<d[j])d[j]=d[j-w[i]]+p[i]; } } if(d[e]==1e9)printf("This is impossible.\n"); else printf("The minimum amount of money in the piggy-bank is %d.\n",d[e]); } return 0; }
多重背包,每种物品个数有限制,用一个数组标记该值是否可达
最后从中间像两边扫描,先找到的值为A和B中的一个
#include <cstdio> #include <string.h> using namespace std; int n,tot; int v[55],m[55]; bool d[250005]; int main(){ while(scanf("%d",&n)&&n>=0){ int tot=0; for(int i=1;i<=n;i++){ scanf("%d%d",&v[i],&m[i]); tot+=v[i]*m[i]; } //多重背包 每种物品数目固定 DP memset(d,0,sizeof d); d[0]=true; for(int i=1;i<=n;i++){ for(int k=1;k<=m[i];k++){ for(int j=tot;j>=v[i];j--){ if(d[j-v[i]])d[j]=true; } } } //输出答案 int ind=0; for(int i=tot/2,j=tot/2;;i++,j--){ if(d[i]){ind=i;break;} if(d[j]){ind=j;break;} } if(ind<tot-ind)ind=tot-ind; printf("%d %d\n",ind,tot-ind); } return 0; }
多重背包,跟新某一点时选择价值更大的组合
memset(d,-1,sizeof d); d[0]=0; for(int i=1;i<=m;i++){ for(int j=1;j<=c[i];j++){ for(int k=n;k>=p[i];k--){ if(d[k-p[i]]!=-1&&d[k-p[i]]+h[i]>d[k]){ d[k]=d[k-p[i]]+h[i]; } } } }
3.3.4 HDU2955 Robberies
题目看了半天才看懂,就是求小偷最多能偷几个银行,DP使该点不被捉的概率最大
假如偷了N个银行,被捉概率是p1,p2..pn ,则不被捉的概率是(1-p1)(1-p2)...(1-pn)
#include <cstdio> using namespace std; int cas,mi[101],n,tot; double pi[101],p; double d[10000]; int main(){ scanf("%d",&cas); while(cas--){ for(int i=0;i<=10000;i++)d[i]=0; tot=0; d[0]=1; scanf("%lf%d",&p,&n); for(int i=1;i<=n;i++){ scanf("%d%lf",&mi[i],&pi[i]); tot+=mi[i]; } //背包 转换为乘法 使同一个点不被捉的概率最大 for(int i=1;i<=n;i++){ for(int j=tot;j>=mi[i];j--){ if(d[j]<d[j-mi[i]]*(1-pi[i]))d[j]=d[j-mi[i]]*(1-pi[i]); } } for(int i=tot;i>=0;i--){ if(d[i]>=1-p){printf("%d\n",i);break;} } } return 0; }
二维背包,背包中的物品个数有限制
状态表示为d[n][m],n为使用物品的个数,m为背包中物品的个数
注意循环的方向,要保证d[n][m]中有m件物品,最后d[N][M]即为所求
#include <cstdio> #include <string> #include <math.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; int cas,n,m,l; int w[105],v[105],d[105][1005]; int main(){ scanf("%d",&cas); while(cas--){ scanf("%d%d%d",&n,&m,&l); for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&v[i]); //DP memset(d,-1,sizeof d); d[0][0]=0; for(int i=1;i<=n;i++){ for(int j=m;j>=1;j--){//注意方向,很重要 for(int k=l;k>=w[i];k--){ if(d[j-1][k-w[i]]!=-1)d[j][k]=max(d[j][k],d[j-1][k-w[i]]+v[i]); } } } int r=0; for(int i=1;i<=l;i++)r=max(r,d[m][i]); printf("%d\n",r); } //system("pause"); return 0; }
一开始直接背包DP,当饭卡余额大于5元时才跟新,最后最大值和卡上的钱的差就是余额..但是提交WA了
比如卡上8元,两种商品7元和2元,如果按照7,2的顺序跟新,2元的商品就不会去买了,因为买了7元的商品后余额就只有1元了..加上一个排序就过了,价值大的商品的后买,这样可以保证卡上最后余额最少.
#include <cstdio> #include <string.h> #include <algorithm> using namespace std; int main(){ int n,p[1005],m,d[1100]; while(scanf("%d",&n),n){ for(int i=1;i<=n;i++)scanf("%d",&p[i]); sort(p+1,p+1+n); scanf("%d",&m); memset(d,0,sizeof d); d[0]=1; for(int i=1;i<=n;i++){ //放宽50因为可能是负的 for(int j=m+55;j>=p[i];j--){ //余额大于5元才可 if(j-p[i]<=m-5&&d[j-p[i]])d[j]=1; } } for(int i=m+55;i>=0;i--){ if(d[i]==1){ printf("%d\n",m-i); break; } } } return 0; }
这题与3.3.4类似,背包的对象也是概率,是不能拿到Offer的概率最小p=(1-a1)(1-a2)...(1-an)
3.3.8 HDU3466 Proud Merchants
带有贪心性质的背包,按Q-P的值排序后再背包即可