poj 1742 Coins(二进制拆分+bitset优化多重背包)

$ Coins $



$ solution: $

这道题很短,开门见山,很明显的告诉了读者这是一道多重背包。但是这道题的数据范围很不友好,它不允许我们直接将这一题当做01背包去做。于是我们得想一想优化。

** $ bitset $ 优化:** 这个是我最先想到的,因为这道题只牵扯到了能不能买,就是一个“是”和“否”的问题,也就是说这个背包并没有什么权值(只有“可以”和“不可以”)然后就是单纯的状态转移。而这不是我们的二进制最擅长的东西吗?(我们利用某一个硬币的面额进行更新时,直接用二进制的左右移和或运算即可)不过这一题的范围有一点大,所以我们选择 $ bitset $

二进制拆分优化: 这个还是得学的,不过他确实很奇妙,就像我们可以用2的一些乘方来表达出所有的数一样,我们可以将这个思想带到多重背包里来。但是我们的二进制能表达出所有的数,而我并不一定需要它表示出那么多的数啊(比如 $ 2^0,2^1dots 2^7,2^8 $ 可以表示出256以内所有的数,但我只要23以内怎么办? )!而解决这个问题就是二进制拆分最难思考的地方了。

二进制拆分的重点,博主决定举个例子,能理解这个例子很多问题就能迎刃而解了:我现在有一个集合 $ {2^0,2^1dots 2^7,2^8 } $ 我可以用它表示出256以内的任何数,而如果我只想要它表示出189以内的数,那我只需要将集合里最大的数改为 $ 189-2^7 $ 即可也就是 $ {2^0,2^1dots 2^7,189-2^7 } $ 可以表示出189以内的任何数。这里的证明留给大家,因为博主自己学习的时候发现如果单纯看证明理解似乎不如手算去感性理解。然后说明一下这个方法其实有一个地方需要注意(也是迷惑人最多的地方):博主为什么选择了从 $ 2^0dots 2^7 $ 为什么又选了189这个数字,其实是因为必须满足向 $ 2^p $ 这个集合中最大的数一定是要比189这样的常数 $ k $ 小的最大的2的乘方!



$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<bitset>

#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int

using namespace std;

bitset<100005> f;

int n,m,ans;
int a[105];

inline int qr(){
	register char ch; register bool sign=0; rg res=0;
	while(!isdigit(ch=getchar())) if(ch=='-')sign=1;
	while(isdigit(ch)) res=res*10+(ch^48),ch=getchar();
	return sign?-res:res;
}

int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	while((n=qr())&&(m=qr())){
		f&=1; f[0]=1; ans=0;
		for(rg i=1;i<=n;++i) a[i]=qr();
		for(rg i=1;i<=n;++i){ rg x=qr();
			for(rg j=1;j<=x;x-=j,j<<=1)
				if(a[i]*j<=m)f|=f<<(a[i]*j);
			if(x*a[i]<=m)f|=f<<(x*a[i]);
		}for(rg i=1;i<=m;++i) if(f[i])++ans;
	    printf("%d
",ans);
	}
	return 0;
}

原文地址:https://www.cnblogs.com/812-xiao-wen/p/10998994.html