这是我们作业上的一道题,也是我认为挺好玩的一道题,是裸的多重背包,不过它只是单纯的让我判断能否装满。我第一次交TLE了,我以为作业题的数据不会很强,干脆偷了个懒枚举了下选的个数,没有二进制优化直接超时了,低估出题老师了~所以我又加上了二进制优化,经历一番坎坷才过。
关于这个背包的知识,我想多说一点,毕竟越是基础的东西越是要加强理解啊……
首先,说01和完全背包,这两个都是很基础的背包,他们两个的区别在于是由当前状态转移而来还是由上一个状态转移而来,由当前状态转移而来代表可是选无数个,就是完全背包,而由上一个状态而来就是只能选一个了,就是01背包,这个如果是二维的,你怎么选无所谓,但是如果是一维的话,就必须从后向前选,因为我们要用前一个状态,而在判断dp[j-v[i]]的时候,这个正好就是前一个的状态,如果正着选,那用的正好就是当前的状态,也就正好就是完全背包。
其实,如何判断是否装满,这个我们需要一个标记,我曾经看到一个人的博客,他是用-1初始化状态,当状态转移的时候如果前一个状态等于-1,就不可以被转移,这样最后不是-1的点就是能恰好被装满的点,判断最后一个点即背包容量是否为-1就是能否装满的判断方法了。但是我更加推荐的是初始化为-inf的方法(dp[0] = 0),它避免了判定条件,这样们判断他最后是否<=0就可以了,注意一定是-inf,如果负值太小最后加成正值就尴尬了。。。关于这种方法,我看到一个人的博客说最后一个点没有被改变,我想说这种说法是错的,他一定被改变了,一定变大了,但还是负的或者说还是负无穷。总之,判断方法就是标记方法,怎么标记取决于题目要求和个人习惯。
然后,我想说一下,背包的路径输出,其实是方案输出,习惯了……这种题目从来没有见过,但我还是提一下吧,可以定义一个path的二维数组,当满足转移条件的时候记录一下,最后倒着输出(因为我们正着取的),01背包输出一个i--一次,完全背包直到这个位置为空的时候才i--(完全背包这个是我的猜想,没有有力的证明,但是01背包是对的)。
最后,回归正题。多重背包的二进制优化,分情况讨论,如果num*cost >= 背包容量就当成完全背包来处理就可以了,反之则转成二进制优化的方法,把多个物品看成一个物品,按照01背包的方式去做。
代码如下:
#include<cstdio> #include<queue> #include<stack> #include<iostream> #include<cstring> using namespace std; #define maxn 2000010 long long dp[maxn],w[maxn],num[maxn],all; int main() { int t,n,m; scanf("%d",&t); while(t--) { scanf("%d%lld",&n,&all); for(int i = 0; i < n; i++) { scanf("%lld%lld",&num[i],&w[i]); } for(int i = 1; i <= all; i++) dp[i] = -9999999; dp[0] = 0; for(int i = 0; i < n; i++) { int tmp = num[i] * w[i];///为什么说坎坷 if(tmp >= all) { for(int j = w[i]; j <= all; j++)///就是这里把w[i]写成了tmp…… { dp[j] = max(dp[j],dp[j-w[i]] + 1); } } else { int k = 1; tmp = num[i]; while(k < tmp) { for(int j = all; j >= k*w[i]; j--) dp[j] = max(dp[j],dp[j-k*w[i]] + k); tmp -= k; k <<= 1; } for(int j = all; j >= tmp*w[i]; j--) dp[j] = max(dp[j],dp[j-tmp*w[i]] + tmp); } } if(dp[all] < 0) printf("false\n"); else printf("true\n"); } return 0; }