背包问题的二进制优化

关于二进制优化这一点,它为什么正确,为什么合理,凭什么可以这样分,至少我是花了很久很久才理解的,先拿一道题来说吧。

HDU 2844 Coins

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2844

题目:

Coins

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 10769    Accepted Submission(s): 4283


Problem Description
Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse and found there were some coins. He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.

You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
 

Input
The input contains several test cases. The first line of each test case contains two integers n(1 ≤ n ≤ 100),m(m ≤ 100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1 ≤ Ai ≤ 100000,1 ≤ Ci ≤ 1000). The last test case is followed by two zeros.
 

Output
For each test case output the answer on a single line.
 

Sample Input
  
  
3 10 1 2 4 2 1 1 2 5 1 4 2 1 0 0
 

Sample Output
  
  
8 4
 

代码奉上

#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstdio>
#include<cstring>
#define maxn 100005
using namespace std;
int dp[maxn],a[105],c;
int    main(){
    int n,m;
    while(cin>>n>>m){
        if(n==0 || m==0) break;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)                             
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++){                    //物品件数遍历
            scanf("%d",&c);
            if(m<c*a[i]){                              //判断是否可以当成完全背包问题来解决单个物品体积×件数>=容量
                for(int k=0;k<=m;k++)          //完全背包
                    if(k>=a[i])
                        dp[k]=max(dp[k],dp[k-a[i]]+a[i]);
            }
            else{
            for(int j=1;j<=c;j<<=1){            //分堆过程
                for(int k=m;k>=0;k--)           //一次性询问2^j的01背包
                    if(k>=a[i]*j)
                        dp[k]=max(dp[k],dp[k-a[i]*j]+a[i]*j);
                c-=j;
            }
            if(c>0){                                      //小优化所在,给你一个眼神儿自己体会
                for(int k=m;k>=0;k--)
                    if(k>=a[i]*c)
                        dp[k]=max(dp[k],dp[k-a[i]*c]+a[i]*c);
            }
        }
        }
        int ans=0;
        for(int i=1;i<=m;i++){
            if(dp[i]==i)
                ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

好吧,貌似确实应该写一套背包的模板出来用了,这样写看着挺烦的,不过这不是重点,重点是,为什么可以进行二进制优化?

我们首先确认三点:

(1)我们知道转化成01背包的基本思路就是判断我是取了你好呢还是不取你好。

(2)我们知道任意一个实数可以由二进制数来表示,也就是2^0~2^k其中一项或几项的和。

(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。

三者综合,然后在纸上深究一下,大概就能理解了。

如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好,假如10个取7个好,那么在实际的遍历过程中在第七个以后经过状态转移方程其实已经是选择“不取”好了,现在,用二进制思想将其分堆,分成k+1个分别有2^k个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而根据第二点任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的在分堆的选择过程中分成了2^0=1,2^1=2,2^2=4,2^3=8这四堆,然后去问四次,也就是拿去走dp状态转移方程,走的结果是第一堆1个,取了比不取好,第二堆2个,取了比不取好,第三堆四个,取了比不取好,第四堆8个,取了还不如不取,最后依旧是取了1+2+4=7个,为什么是这样呢?因为dp本身就是用来比较哪个更优的,在状态转移的过程中自然会完成上述询问得出相应结果。这也就是说,无论最终取几个是最优解,用二进制取出来的结果和一次一次问是完全一样的。相信已经说的足够详细清楚了。重要还是自己体会一下为什么这样。

理解之后看代码发现事实上在写代码的过程中又搞了一丢丢小优化,自己感觉一下吧~

有没有被二进制惊艳到?就这样复杂度顿时降了一个档次,瞬间感觉世界真奇妙~~~神奇的二进制~~~ok,打字打的好累,休息一会儿继续写代码~

  • 74
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值