01背包求方案数
问题重述
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式:
输出一个整数,表示 方案数 模 109+7 的结果。
数据范围
0<N,V≤1000
0<vi,wi≤1000
思路分析:
分析过01背包后这道题便也容易解决,我们知道01背包的状态转移方程是 f[ i ][ j ] = max( f[ i -1][ j ] , f[ i-1 ][ j -v[i]]+wi ) 如果我们能记录转移到某种状态的方案数,我们便可求最终的方案数。只需要加一个数组记录每一种状态的方案数,然后加一个判断,跟着状态转移就可。
即如果 f[ i ][ j ] 是由 f[ i -1][ j ] 或 f[ i -1][ j -v[i]]+wi 其中一种状态转移过来的那么它对应的状态方案数就是,转移过来的那个即 counts[ i ][ j ] = counts[ i -1][ j ] 或是 counts[ i ][ j ] =counts[ i -1][ j -v[i]] 如果两者都能转移到当前状态那么当前状态的方案数就是两者之和,即 counts[ i ][ j ] = counts[ i -1][ j ] + counts[ i -1][ j -v[i]] 。 那么它对应的边界情况也比较清晰即 i 或 j 为 0 时 counts[ i ][ j ]=1 因为此时 f[ i ][ j ]=0 所以对应的方案数量就是 1 就只有它本身,因为它是边界没有办法从它处转移过来。
当然在01背包问题中我们将状态转移方程最终优化到了 f[ j ] = max( f[ j ] , f[ j -v[i]]+wi ), 所以对应的我们用来记录方案的数组数组也可以对应降到一维,即 counts[ j ] = counts[ j ] 或是 counts[ j ] =counts[ j -v[i]] 或是 counts[ j ] = counts[ j ] + counts[ j -v[i]]。
C++代码:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N,V;
vector<int>ans,counts;
cin>>N>>V; //录入物品数量及背包容积
ans.resize(V+10); //将记录答案的数组开辟合适的空间一般多开几个
counts.resize(V+10,1); //为记录方案的数组开辟合适的空间同时赋初值为1
for(int i=0;i<N;i++) //开始循环物品
{
int volume,value;
cin>>volume>>value; //现场输入当前物品的体积和价值
for(int j=V;j>=volume;j--) //开始从大到小遍历体积
{ //因为优化到了一维转移问题变成了覆盖问题
if(ans[j] < ans[j-volume]+value) //如果当前值需要覆盖即覆盖两个数组相应值如果
{ //ans[j] > ans[j-volume]+value 即不需覆盖因为一维的特性什么也不需做
ans[j]=ans[j-volume]+value;
counts[j]=counts[j-volume];
}
else if(ans[j] == ans[j-volume]+value) //最特殊的当二者相同时ans[j]也不需覆盖
{ //但counts[j]要变成二者之和因为两种状态都能转移过来
counts[j]=(counts[j]+counts[j-volume])%1000000007;//对应题目取模
}
}
}
cout<<counts[V]<<endl; //输出结果
return 0;
}