[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-2 +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
",(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我们一起探讨哦

原文地址:https://www.cnblogs.com/dixiao/p/11415941.html