[Bzoj 1192][HNOI2006]鬼谷子的钱袋(二进制优化多重背包)
(人生第一篇bzoj题解有点激动
首先介绍一下题目:
看它题目那么长,其实意思就是给定一个数a,求将其拆分成n个数,通过这n个数可以表示出1~a中所有数的方案中,求最小的n。
您看懂了嘛?不懂咱来举个栗子:
3可以变为(1,2)两个数(废话,我当然知道),使1,2可以表示出1,2,3这些数字。看到这道题就想到了不久前看到的二进制优化多重背包。简直一模一样。
我们想我们对于一个数P。我们可以将其分成两部分(1~P/2)与(P/2+1,P),我们思考:假设我们已经知道(1~P/2)至少需要m个数,那么我们完全可以在左区间的基础上添加一个P/2这个数,使这m+1个数可以表示(1~P)的所有数,因为(1+P/2~P/2+P/2)=(P/2+1~P)。
我们其实又可以通过手+草稿纸发现关于一个P的最小值n是有(1~P)的左半边区间(1~P/2)的最小值+1有关,而(1~P/2)的最小值又与它本身的左半边区间的值有关,所以事实上这是一个可以递推的过程。
然后我们归纳总结一下——P与P/2有关,P/2又与P/4有关,很容易联想到二进制。我们只要手算过后稍一扩展我们便会发现——关于一个数P,我们使一个数s满足2s -1<P,注意严格小于P,即s为log2(P),那么这一个数P可以被1,2,4.......2s-1,P-2s +1 分解。
那么其实我们很快就可以知道一个数P所对应最小n了,其实就是log2(P)+1.那么这道题一下变得简单了起来,贴代码
#include<iostream> #include<cmath> #include<cstdio> using namespace std; int main() { long long a; scanf("%lld",&a); printf("%lld",(long long )log2(a)+1); }
其实这道题在luogu上有加强版本https://www.luogu.org/problem/P2320,其要求输出每个钱袋的数量,这道题也很简单,我们通过上面的推理先求出S,再以此求出1~2s-1 和P-2s+1再排序输出即可(据说原本只有算n,luogu加上了这道题,然而数据貌似有锅)
#include<iostream> #include<cstdio> #include<cmath> using namespace std; long long p,s,k; int main() { scanf("%lld",&p); printf("%lld\n",(long long)log2(p)+1); s=log2(p); k=p-(1<<s)+1; for(long long i=0;i<=s-1;i++) { if(1<<i>k&&k!=0) { printf("%lld ",k); k=0; } printf("%lld ",1<<i); } if(k!=0) printf("%lld ",k); return 0; }
接下来简单描述一下标题所写的二进制优化多重背包
一般来说我们的多重背包的思路是枚举取n个同种物品,转化为01背包
那这样的时间复杂度 O(NV*∑(V/W[i])).(V为体积,W[i]为每件的费用)
但假如我们将每件物品都像鬼谷子的钱袋一样二进制拆分,那每一个物品都会被拆成log2(W[i])+1个物品
那这样再做01背包所要的时间复杂度为 O(V*log(W[i])+V)(PS:这个时间复杂度有可能写错了,但是应该差不多)
关于二进制优化多重背包以后我会再写一篇博客来讲,这篇主要的是题解hhh
您理解了嘛,慢走。
不理解的可以加qq2733524923我们一起探讨哦