hdu 3033 I love sneakers!

题目 

http://acm.hdu.edu.cn/showproblem.php?pid=3033

分析

    这题一看就是带分组的01背包问题,而且题目限制每组至少取1个,而不是最多1个,不符合分组背包至多取一个的思路,但是感觉可以从分组背包的解题代码框架中修改过来。
《背包九讲》中分组背包框架:

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);

复杂度分析

dp[i][j] = max(dp[i][j],dp[i-1][j-cost[i][k]] + val[i][k],dp[i][j-cost[[\i][k]] + val[i][k]) (dp[i][j]表示选到i组填到容量j的最大价值,cost[i][k]表示第i组第k组的花费,val同上)。总的时间复杂度复杂度大于O(NM),空间复杂度是O(KM)。

代码

经过整理程序的解题过程如下所示:

#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;
}



你可能感兴趣的:(动态规划,application)