01背包 多重背包 复习 模板
HDU 2126 01背包方案计数
http://acm.hdu.edu.cn/showproblem.php?pid=2126
题意 n个纪念品 m块钱 问m块钱最多能买几个纪念品并输出方案数。
解析 01背包的方案数我们可以统计 但是要知道物品数量 所以要多开一维记录信息
dp[ i ][ k ][ j ]表示 前i个物品 花 j 元钱 买k个物品的方案数
dp[ i ][ k ][ j ] = dp[ i-1 ][ k ][ j ] + dp[ i-1][ k-1 ][ j-v[i] ];
AC代码
#include <bits/stdc++.h> #define pb push_back #define mp make_pair #define fi first #define se second #define all(a) (a).begin(), (a).end() #define fillchar(a, x) memset(a, x, sizeof(a)) #define huan printf("\n") #define debug(a,b) cout<<a<<" "<<b<<" "<<endl #define ffread(a) fastIO::read(a) using namespace std; typedef long long ll; const int maxn = 6e3+10; const int inf = 0x3f3f3f3f; const ll mod = 1000000009; const double epx = 1e-6; const double pi = acos(-1.0); //head------------------------------------------------------------------ ll dp[35][35][505],v[35]; int main() { int t,n,m; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); fillchar(dp,0); for(int i=1;i<=n;i++) scanf("%lld",&v[i]); for(int i=0;i<=m;i++) for(int j=0;j<=n;j++) dp[j][0][i]=1; for(int i=1;i<=n;i++) { for(int k=1;k<=i;k++) { for(int j=1;j<=m;j++) { if(j>=v[i]) dp[i][k][j]=dp[i-1][k][j]+dp[i-1][k-1][j-v[i]]; else dp[i][k][j]=dp[i-1][k][j]; } } // for(int j=1;j<=m;j++) // { // for(int k=1;k<=i;k++) // cout<<k<<" "<<j<<" "<<dp[i][k][j]<<" "; // cout<<endl; // } } int flag=0; for(int i=n;i>=1;i--) { if(dp[n][i][m]>0) { printf("You have %lld selection(s) to buy with %d kind(s) of souvenirs.\n",dp[n][i][m],i); flag=1; break; } } if(!flag) printf("Sorry, you can't buy anything.\n"); } }
HDU 2079
http://acm.hdu.edu.cn/showproblem.php?pid=2079
题意 n个学分 k种课 第 i 种课的学分为ai 数量为bi 问刚好修满n个学分的选课方案数(相同种类的课程没有区别)
解析 这道题可以用母函数写。但是我们考虑多重背包怎么写,很容易想到转换成01背包,但是不作处理直接一个一个枚举会有计数重复的情况。
考虑选修第 i 类课的话 我们直接枚举数量 从大到小更新 从而避免重复计数
dp[ k ] += dp[ k-j*v[ i ] ] 不可以dp[ k ] += dp[ k-j*v ] 因为求的是方案数不是最大值,不然会有重复。
AC代码
#include <bits/stdc++.h> #define pb push_back #define mp make_pair #define fi first #define se second #define all(a) (a).begin(), (a).end() #define fillchar(a, x) memset(a, x, sizeof(a)) #define huan printf("\n") #define debug(a,b) cout<<a<<" "<<b<<" "<<endl #define ffread(a) fastIO::read(a) using namespace std; typedef long long ll; const int maxn = 6e3+10; const int inf = 0x3f3f3f3f; const ll mod = 1000000009; const double epx = 1e-6; const double pi = acos(-1.0); //head------------------------------------------------------------------ int v[10],num[10],dp[50]; int main() { int t,n,m; scanf("%d",&t); while(t--) { fillchar(dp,0); scanf("%d%d",&m,&n); for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&num[i]); dp[0]=1; for(int i=1;i<=n;i++) { for(int k=m;k>=v[i];k--) { for(int j=1;j<=num[i];j++) { if(k>=j*v[i]) dp[k]+=dp[k-j*v[i]]; } } } printf("%d\n",dp[m]); } }
HDU2191
http://acm.hdu.edu.cn/showproblem.php?pid=2191
题意 多重背包求最大值 裸题
解析 可以转化成01背包O( v*m*sum_num[i] ) 二进制优化O( v*m*sum_log( num[i] ) ) 单调队列O( v*m )
二进制
#include <bits/stdc++.h> #define pb push_back #define mp make_pair #define fi first #define se second #define all(a) (a).begin(), (a).end() #define fillchar(a, x) memset(a, x, sizeof(a)) #define huan printf("\n") #define debug(a,b) cout<<a<<" "<<b<<" "<<endl #define ffread(a) fastIO::read(a) using namespace std; typedef long long ll; const int maxn = 6e3+10; const int inf = 0x3f3f3f3f; const ll mod = 1000000009; const double epx = 1e-6; const double pi = acos(-1.0); //head------------------------------------------------------------------ int n,m; //n为物品的种类数,m为背包最大承载重量 int dp[1005]; // dp状态数组 int num[1005]; //物品的数量 int value[1005]; //物品的价值 int weight[1005];//物品的重量 /* 多重背包问题的二进制解法 */ void zeroonebag(int heft,int worth) //01背包 { //heft为当前物品的重量,worth为当前物品的价值 for(int i=m;i>=heft;i--) dp[i]=max(dp[i],dp[i-heft]+worth); } void completeBag(int heft,int worth) //完全背包 { //heft为当前物品的重量,worth为当前物品的价值 for(int i=heft;i<=m;i++) dp[i]=max(dp[i],dp[i-heft]+worth); } int main() { int t; cin>>t; while(t--) { memset(dp,0,sizeof(dp)); //初始化dp数组 只求最大值 //memset(dp,-inf,sizeof(dp));dp[0]=0; 要求恰好装满求最大值 最小值设为inf cin>>m>>n; //n物品种类 m总容量 for(int i=0;i<n;i++) cin>>weight[i]>>value[i]>>num[i]; //输入物品的重量 输入物品的价值 输入物品的个数 for(int i=0;i<n;i++) //遍历每一类物品 { if(weight[i]*num[i]>=m) { //如果同一类物品的总重量大于背包的最大容量,则转换成完全背包 completeBag(weight[i],value[i]); } else //多个01背包 { for(int t=1;t<=num[i];t*=2) //二进制的运用 { zeroonebag(weight[i]*t,value[i]*t); num[i]-=t; } zeroonebag(weight[i]*num[i],value[i]*num[i]); } } cout<<dp[m]<<endl; //输出结果 } return 0; }
单调队列 并未获得该技能包。。。QAQ 技能包
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #define ll long long using namespace std; int n,m,he,ta,T; ll f[10010],q[10010],num[10010]; int main() { int i,j,w,v,s,d; scanf("%d",&T); while(T--) { scanf("%d%d",&m,&n); for(i=0; i<=m; i++) f[i]=0; for(i=1; i<=n; i++) { scanf("%d%d%d",&w,&v,&s); if(s>m/w) s=m/w; for(d=0; d<w; d++) { he=ta=1; for(j=0; j<=(m-d)/w; j++) //先存进去,后取出来 { int tmp=f[j*w+d]-v*j; while(he<ta&&q[ta-1]<=tmp) --ta; q[ta]=tmp,num[ta++]=j; while(he<ta&&j-num[he]>s) ++he; f[j*w+d]=max(f[j*w+d],q[he]+v*j); } } } printf("%lld\n",f[m]); } return 0; }