背包 基础题集
HDU 1171 杭电分设备(多重背包)
题意:给一组物品,要求分给A,B 2个人,要求A分到的价值总和不小于B,输出A,B分到的价值总和。
题解:多重背包问题:思路比较简单,设sum为物品的总价值
方法1:直接打背包价值的表F[V](装满背包初始化为-INF),从v=sum/2往右找,满足F[v]>0的即可;
方法2:设背包容量为V=sum/2,直接找F[V]即可;
反思:当价值和容量是同一种的时候,基本都有2种方法,初始化为-INF,找满足条件下的因变量下对于的自变量x(什么样容量的背包可以被装满),
或初始化为0(这个背包最多可以装下的价值),找因变量;(把背包问题看成函数求最优解,x为定义域(容量),y为值域(价值总和等));
方法1代码:
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int maxn=2e6+5; const int INF=0x3f3f3f3f; int f[maxn], c[maxn], w[maxn]; int main() { //freopen("in.txt", "r", stdin); int n; while (cin>>n, n>=0) { int all_n=1, sum=0; while(n--) { int val,num,k; cin>>val>>num; sum=sum+val*num; for(k=1; k<num; k<<=1){ w[all_n++]=k*val; num=num-k; } if(num) w[all_n++]=num*val; } for(int v=0; v<=sum; v++) f[v]=-INF; f[0]=0; for(int i=1; i<all_n; i++) for(int v=sum; v>=w[i]; v--) f[v]=max(f[v], f[v-w[i]]+w[i]); int tmp; if(sum%2) tmp=sum+1; else tmp=sum; int ans=0; for(int v=tmp/2; v>=0; v++) if(f[v]>0){ ans=v; break; } cout<<ans<<" "<<sum-ans<<endl; } return 0; }
方法2代码:
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int maxn=2e6+5; const int INF=0x3f3f3f3f; int f[maxn], c[maxn], w[maxn]; int main() { //freopen("in.txt", "r", stdin); int n; while (cin>>n, n>=0) { int all_n=1, sum=0; while(n--) { int val,num,k; cin>>val>>num; sum=sum+val*num; for(k=1; k<num; k<<=1){ w[all_n++]=k*val; num=num-k; } if(num) w[all_n++]=num*val; } memset(f, 0, sizeof(f)); for(int i=1; i<all_n; i++) for(int v=sum/2; v>=w[i]; v--) f[v]=max(f[v], f[v-w[i]]+w[i]); printf("%d %d\n", sum-f[sum/2], f[sum/2]); } return 0; }
HDU 2844 硬币(多重背包)
题意:给你物品,求这些物品可以组成多少种的价值(背包容量有限制的);
题解:和上题很相似有2种方法,但是直接初始化为0,再循环打表,循环遍历,输入到set中,通过set去重,再输出答案,(我写的代码是TLE了,有博客上是990ms过了)
明显初始为-INF,求什么容量的背包可以被装满要简单不少,这样就不用set去重了(680ms过);
反思:有思路的时候不要急于去实现代码,可以再多思考那么一点,是否可以优化,避免不必要的运算;
还有个问题,通过这题发现,能不用fill就不要用fill,这个函数并不适合于ACM,更适合于做做工程;
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int INF=0x3f3f3f3f; const int maxn=2e6+5; int f[maxn], a[105], b[105]; int main() { //freopen("in.txt", "r", stdin); int n, V; while(cin>>n>>V, n+V) { for(int i=1; i<=n; i++) scanf("%d", &a[i]); for(int i=1; i<=n; i++) scanf("%d", &b[i]); for(int i=0; i<=V; i++) f[i]=-INF; //688msAC //fill(f, f+V, -INF); //超时1000ms,TLE //我tm是服了...查了一下午 f[0]=0; for(int i=1; i<=n; i++) { int t=b[i], k=1; while(k<t){ for(int v=V; v>=k*a[i]; v--) f[v]=max(f[v], f[v-k*a[i]]+k*a[i]); t=t-k; k<<=1; } if(t) for(int v=V; v>=t*a[i]; v--) f[v]=max(f[v], f[v-t*a[i]]+t*a[i]); } int ans=0; for(int v=1; v<=V; v++){ //printf("f[%d]=%d\n",v,f[v]); if(f[v]>0) ans++; } cout<<ans<<endl; } return 0; }
HDU 3033 (有限制的分组背包问题)
题意:给出物品(有分组的),从每组选一个(背包容量有限),求最大的权值和;
题解:分组背包问题,不过有个限制(和学的背包九讲不同),每组必须一个,又有点二维背包的意思(有一个维度必须装满),二维数组实现,直接想状态转移还是不难的;
f[i][v]=max(max(f[i][v], f[i][v-c[j]]+w[j]), f[i-1][v-c[j]]+w[j]); ( i是指组 )
方程不难想,但是转移的时候这个是有顺序的,优先考虑从这一层转移来的,再考虑从上一层转移来的(不然会有重复运算),直接max(状态1, 状态2, 状态3)也行;
反思:这些题目不会写的时候 要回归dp问题,找状态和决策,再找状态转移方程
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int INF=0x3f3f3f3f, maxn=105; int c[maxn], kind[maxn], w[maxn], f[maxn][10005]; int main() { //freopen("in.txt", "r", stdin); int n,m,k; while(cin>>n>>m>>k) { fill(f[0], f[0]+maxn*10005, -INF); memset(f[0], 0, sizeof(f[0])); for(int i=1; i<=n; i++) cin>>kind[i]>>c[i]>>w[i]; for(int i=1; i<=k; i++) for(int j=1; j<=n; j++) if(kind[j]==i) for(int v=m; v>=c[j]; v--) f[i][v]=max(max(f[i][v], f[i][v-c[j]]+w[j]), f[i-1][v-c[j]]+w[j]); if(f[k][m]<0) cout<<"Impossible"<<endl; else cout<<f[k][m]<<endl; } return 0; }
HDU 3732 (01背包要优化)
题意:给了n个物品,n很大,求出满足背包容量v的最大权值和;
题解:一眼扫去,01背包,敲代码,TLE;观察数据发现,n*n的(n有点大)复杂度肯定超时间,
题目所给的c和w比较小,但是n很大,可以尝试化为多重背包写,运用2进制的思想压缩物品来优化时间复杂度;
反思:看到题目给的数据就应该要意识到大概需要什么样的复杂度;
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int maxn=1e4+5; int g[15][15], f[maxn]; int main() { //freopen("in.txt", "r", stdin); int n,V; while (cin>>n>>V) { memset(g, 0, sizeof(g)); while(n--){ getchar(); char s[15]; scanf("%s",s); int w,c; scanf("%d%d",&w, &c); g[w][c]++; } memset(f, 0, sizeof(f)); for(int w=0; w<=10; w++) for(int c=0; c<=10; c++) { int s=g[w][c]; int t=1; while(t<s){ for(int v=V; v>=t*c; v--) f[v]=max(f[v], f[v-t*c]+t*w); s=s-t; t=2*t; } if(s!=0) for(int v=V; v>=s*c; v--) f[v]=max(f[v], f[v-s*c]+s*w); } cout<<f[V]<<endl; } return 0; }
HDU 1955(抢银行的概率)
题意:每一个银行都有一个被抓的概率和能获得的钱,求在可以可以忍受的概率下能抢劫到的最多的钱;
题解:01背包,但是有了个浮点数, 需要转化,有2个问题:1.背包容量的概率,浮点数,需要转化背包的容量和价值;2.概率的计算问题,被抓的概率算不被抓的概率方便一些,注意是乘法;
#include <iostream> #include <cstdio> #include <cstring> using namespace std; double f[1000007]; struct BANK{ int c; double p; }bank[107]; int main() { //freopen("in.txt", "r", stdin); int T; cin>>T; while(T--) { double val; cin>>val; val=1-val; //逃跑的概率 int n; cin>>n; int V=0; for(int i=1; i<=n; i++) { cin>>bank[i].c>>bank[i].p; V+=bank[i].c; } memset(f, 0, sizeof(f)); f[0]=1.0; int ans=0; for(int i=1; i<=n; i++) for(int v=V; v>=bank[i].c; v--) { //f[v]=max(f[v], f[v-bank[i].c]+bank[i].p); 错解,数学是真垃圾呀! f[v]=max(f[v], f[v-bank[i].c]*(1-bank[i].p)); /*if(i==n&&f[v]>val){ ans=v; break; }*/ } for(int v=V; v>=0; v--) { if(f[v]>val){ ans=v; break; } } cout<<ans<<endl; } return 0; }
HDU 3496 买电影票的
题意:每次只能买m张电影票,求用V的钱,能买到的电影的最大价值;
题解:这题是个二维背包,而且是有一个维度需要装满,初始化为-INF打个表,再找一下v1=m,v2 <V的最大价值即可,我看有些博客初始化为-1,单独判断==-1和初始化为-INF的思路都一样,判断是否由合法的状态转移来的。
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=107; const int INF=0x3f3f3f3f; int f[maxn][1007], c2[maxn], w[maxn]; int main() { //freopen("in.txt", "r", stdin); int T; cin>>T; while(T--) { for(int i=0; i<maxn; i++) for(int j=0; j<1007; j++) f[i][j]=-INF; int n,V1,V2; cin>>n>>V1>>V2; f[0][0]=0; for(int i=1; i<=n; i++) cin>>c2[i]>>w[i]; for(int i=1; i<=n; i++) for(int v1=V1; v1>=1; v1--) for(int v2=V2; v2>=c2[i]; v2--) f[v1][v2]=max(f[v1][v2], f[v1-1][v2-c2[i]]+w[i]); int ans=0; for(int i=0; i<=V2; i++) ans=max(ans, f[V1][i]); cout<<ans<<endl; } return 0; }
初始化为-1的情况:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=107; int f[maxn][1007], c2[maxn], w[maxn]; int main() { //freopen("in.txt", "r", stdin); int T; cin>>T; while(T--) { memset(f, -1, sizeof(f)); //-1方便后面的判断,题目有个条件:只卖m部(装满背包),状态转移要注意 int n,V1,V2; cin>>n>>V1>>V2; f[0][0]=0; for(int i=1; i<=n; i++) cin>>c2[i]>>w[i]; for(int i=1; i<=n; i++) for(int v1=V1; v1>=1; v1--) for(int v2=V2; v2>=c2[i]; v2--) if(f[v1-1][v2-c2[i]]!=-1) //这个判定其实过于严格,所以后面要找一下最大值 f[v1][v2]=max(f[v1][v2], f[v1-1][v2-c2[i]]+w[i]); int ans=0; //维度1要求装满,维度2不要求必须装满,我们是当作转满算的,要找一下最大值 for(int i=0; i<=V2; i++) ans=max(ans, f[V1][i]); cout<<ans<<endl; } return 0; }
初始话为-INF,第一行为0,不需要遍历找ans;
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int MAXN = 1e7; const int INF=0x3f3f3f3f; int dp[111][1111]; int main() { int t; cin >> t; while (t--) { //memset(dp, -1, sizeof(dp)); int n, m, l; cin >> n >> m >> l; vector<int> w(n), v(n); for (int i = 0; i < n; ++i) { cin >> w[i] >> v[i]; } for(int i=0; i<111; i++) for(int j=0; j<1111; j++) dp[i][j]=-INF; for (int i = 0; i <= l; ++i) { dp[0][i] = 0; } for (int i = 0; i < n; ++i) for (int j = m; j >= 1; --j) for (int k = l; k >= w[i]; --k) dp[j][k] = max(dp[j][k], dp[j - 1][k - w[i]] + v[i]); cout << (dp[m][l] <0 ? 0 : dp[m][l]) << endl; } return 0; }
HDU 2546 饭卡食堂买菜
题解:有特殊情况的01背包,
读题没怎么弄明白,这个买饭是一次买一个菜,好像和现实不太一样??
因为剩下的五块可以买任意价值的物品,所以在进行递推的过程中先将总额减去5块,进行n-1个菜的递推
(菜价从低到高排好序,排序是为了最后的5元能取得最大值)。
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxn=1007; int f[maxn], c[maxn]; int main() { int n; while(cin>>n, n) { memset(c, 0, sizeof(c)); memset(f, 0, sizeof(f)); for(int i=1; i<=n; i++) cin>>c[i]; int m; cin>>m; if(m<5){ cout<<m<<endl; continue; } sort(c+1, c+1+n); for(int i=1; i<=n-1; i++) for(int v=m-5; v>=c[i]; v--) f[v]=max(f[v], f[v-c[i]]+c[i]); cout<<m-f[m-5]-c[n]<<endl; } return 0; }
HDU 1864 发票报销
题解:也是有浮点数的情况,是把浮点型放大成int形,再01背包即可,
但是此题的输入很麻烦,这地方我很菜很菜搞了好久,(这种题目一定好好琢磨样例,那些输入是会有问题的)要加强,
有%c时,注意吸收键盘缓冲区,读题的时候也要注意所给的输入,什么是合法的什么是不合法,输入都会有那些情况;
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int maxn=3e6+7; int w[37], f[maxn]; int main() { //freopen("in.txt", "r", stdin); double Q; int n; while(cin>>Q>>n, n) { int V=Q*100; int all_n=0; while(n--) { int m; cin>>m; char ch; double data; int sum=0, va=0, vb=0, vc=0, flag=1; while(m--) { getchar(); scanf("%c:%lf", &ch, &data); data=data*100; if(flag) { if(ch=='A'&&va+data<=60000) va=va+data; else if(ch=='B'&&vb+data<=60000) vb=vb+data; else if(ch=='C'&&vc+data<=60000) vc=vc+data; else flag=0; } } sum=va+vb+vc; if(flag&&sum<=100000) w[all_n++]=sum; } memset(f, 0, sizeof(f)); for(int i=0; i<all_n; i++) for(int v=V; v>=w[i]; v--) f[v]=max(f[v], f[v-w[i]]+w[i]); double ans=f[V]/100.0; printf("%.2f\n", ans); } return 0; }