动态规划:完全背包与完全背包计数
有N种物品和一个容量为V的背包,每种物品的数量无限,第i种物品的重量为c[i],价值为w[i],将若干件物品装入背包,求背包所放物品的最大价值
这时每种物品都有取0件,取1件,取2件等若干种情况
采用朴素的实现方式是类比01背包的状态转移方程,我们给出这样的形式,我们令f[i][v]表示前i件物品恰放入一个容量为v的背包时的最大价值
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
与01背包的转移方程相比,这里仅仅是多了一个常数k,表示0,1,2,3件的各种情况
但是这时的时间复杂度是要超过O(V*N)的
我们给出其优化形式,类比01背包的滚动数组的实现思路,滚动数组是倒着滚的,这里正着滚就可以在拥有自己物品的状态上加以继承达到放多件的目的
f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}
将其用滚动数组实现就变成了
f[v]=max{f[v],f[v-c[i]]+w[i]}
这就是解决完全背包问题的最佳状态转移方程
我们给出其实现
1 #include<algorithm> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=2005; 6 const int maxv=50005; 7 int N,V; 8 int v[maxn],w[maxn]; 9 int ans=0; 10 int f[maxv]; 11 int T; 12 int main() 13 { 14 scanf("%d",&T); 15 while(T--) 16 { 17 scanf("%d%d",&N,&V); 18 for(int i=1;i<=N;i++) 19 { 20 scanf("%d%d",&v[i],&w[i]); 21 } 22 memset(f,128,sizeof(f)); 23 f[0]=0; 24 for(int i=1;i<=N;i++) 25 for(int j=v[i];j<=V;j++) 26 { 27 f[j]=max(f[j],f[j-v[i]]+w[i]); 28 } 29 if(f[V]<=0) 30 printf("NO\n"); 31 else 32 printf("%d\n",f[V]); 33 } 34 return 0; 35 } 36
在这里考虑一个细节,那就是满不满包,如果满包,要记得将所有f数组元素设置为负无穷,然后f[0]=0就好了
然后我们考虑其变式形式,来源是货币系统这道题目
题意是这样的,给定了几种货币的面值,然后问你用这几种货币来凑一个钱数,有多少种拼凑方法
因为我们要求的是方案数,这里每一种物品的质量和价值就不那么显然了
我们用f[i]表示面值为i时的方案数,给出转移方程
f[0]=1 f[j]+=f[j-v[i]]
接下来给出完整的实现:
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=30,maxv=10005; 6 int N,V; 7 int v[maxn]; 8 long long ans=0; 9 long long f[maxv]; 10 void dp() 11 { 12 f[0]=1; 13 for(int i=1;i<=N;i++) 14 for(int j=v[i];j<=V;j++) 15 f[j]+=f[j-v[i]]; 16 ans=f[V]; 17 } 18 int main() 19 { 20 cin>>N>>V; 21 for(int i=1;i<=N;i++) 22 cin>>v[i]; 23 dp(); 24 cout<<ans<<endl; 25 return 0; 26 }