题目
http://acm.hdu.edu.cn/showproblem.php?pid=3033分析
for 所有的组k
forv=V..0
for 所有的i属于组k
f[v]=max{f[v],f[v-c[i]]+w[i]}
为了符合本题目至少选一个的目的,感觉应该将上面第二个循环和第三个循环调整顺序,方可遍历本组选1,2....个物品的目的。有了以上的代码框架然后就是寻找状态方程了。有题目可得到:需要实现本组至少选择一个,因此需要遍历本组;需要从上一个分组传传递过俩,因此需要遍历上一个分组;
如果采用01背包的解题框架直接利用f[j]=max(f[j],g[j-b[i]]+c[i]);来合并以上两个决策,则会使题目变成真正的01背包了(因为在处理下一个分组的第一个元素的f[资源上限]时候用的是上一个分组整体处理之后的结果,如果该值很大,则会使第一个分组的该最大值一直保存到整体处理完成,而该处理方式正式01背包的处理方式,即该值不是在本资源限制条件下取本元素之后算出来的)。因此,应该将以上两种决策分开来计算,并且能够至少存储上一个分组的计算结果(即可以对每一个分组开一个数据,保存全部的中间结果;也可以只用两个滚动数组,保存上一个分组的结果和本分组的结果)。本文采用第一种表示方式,比较清晰易懂;得到状态转移方程如下所示:
f[j][v]= max(f[j][v], f[j][v-cost]+value);//表示在本组遍历
f[j][v]= max(f[j][v], f[j-1][v-cost]+value);//表示在上一个分组遍历
并且第一个方程必须在第二方程前面,因为商品存在0花费的情况,如果调换位置当花费为0使,会使该商品在两个方程的的个参数中计算两次造成重复。
在对f数组初始化的时候,应该考虑到商品存在花费为0的情况,故不能初始化为0,否则在出结果为0的时候,会无法分清楚是初始化的还是程序计算的,没有可比较的初始值,那就需要自己再添加变量记录会很麻烦。因此,dp[][]数组初始化为-1表示所有的数都不合法。大于0表示合法,然后将所有的 k = 0 dp[0][]置为0,这是为了 k = 1时能合法计算。在状态转移的时候应该考虑方程第二个参数的状态是否合法,因此得到具体代码为:
if(dp[k][j - m[k][i].m] != -1)
dp[k][j] = Max(dp[k][j] , dp[k][j - m[k][i].m] + m[k][i].v);
if(dp[k-1][j - m[k][i].m] != -1 )
dp[k][j] = Max(dp[k][j] , dp[k-1][j - m[k][i].m] + m[k][i].v);
复杂度分析
代码
经过整理程序的解题过程如下所示:
#include <iostream>
#include <vector>
using namespace std;
vector<pair<long,long> > coll3033[11];
long f3033[11][10001];
void init()
{
for(int i=0;i<11;++i)
coll3033[i].clear();
memset(f3033,-1,sizeof(f3033));
memset(f3033[0],0,sizeof(f3033[0]));//为了使第一组商品可以合法计算
}
int main()
{
int N,M,K;
long ta=0,tb=0,tc=0;
while(cin>>N>>M>>K)
{
init();
while(N--)
{
cin>>ta>>tb>>tc;
coll3033[ta].push_back(make_pair(tb,tc));
}
for(int i=1;i<=K;++i)//所有分组
for(int k=0;k<coll3033[i].size();++k)//每个分组中的商品;
for(int j=M;j>=coll3033[i][k].first;--j)//价格限制;调欢分组背包中这两句位置,是为了实现每组最少选一个,否则就是最多选一个
{
if(f3033[i][j-coll3033[i][k].first]!=-1)//如果不加这两个if就是对N件商品进行01背包,因为第i行初始化为-1
f3033[i][j]=max(f3033[i][j],f3033[i][j-coll3033[i][k].first]+coll3033[i][k].second);//所以本句是否不起作用
if(f3033[i-1][j-coll3033[i][k].first]!=-1)//注意这里的2个if的顺序,如果上面面这个if在上面的话,当体积为0时,相当于,Max( dp[k][j], dp[k][j] + m[k][i].v); dp[k][j]的值很可能会被计算两次。比如我最后给的那个测试例子。输出会是3,不会是正确答案2.
f3033[i][j]=max(f3033[i][j],f3033[i-1][j-coll3033[i][k].first]+coll3033[i][k].second);
}
if(f3033[K][M]>=0)
cout<<f3033[K][M]<<endl;
else
cout<<"Impossible"<<endl;
}
return 0;
}