Coins
有n个硬币,第i个硬币的价值\(a_i\),数量\(c_i\),现在求这些硬币组成的1~m以内的钱的个数,\(1<=n<=100,m<=100000,c_i\leq 1000\)。
解
法一:二进制拆分优化01
注意到这类似多重背包,多个选择,关键在于数据范围过大,于是可以采取二进制拆分优化。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
using namespace std;
bool dp[100001];
int a[101],c[101],w[1001],wt;
il void read(int&);
int main(){
int n,m,i,j,k,ans;
while(read(n),read(m),n&&m){
memset(dp,0,sizeof(dp)),wt&=0;
for(i=1;i<=n;++i)read(a[i]);
for(i=1;i<=n;++i)read(c[i]);
for(i=1;i<=n;++i){
j=1;
while(c[i]-j>=0){
w[++wt]=a[i]*j;
c[i]-=j,j<<=1;
}w[++wt]=a[i]*c[i];
}dp[0]|=true,ans&=0;
for(i=1;i<=wt;++i)
for(j=m;j>=w[i];--j)
dp[j]|=dp[j-w[i]];
for(i=1;i<=m;++i)ans+=dp[i];
printf("%d\n",ans);
}
return 0;
}
il void read(int &x){
x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
法二:阶段内转移优化
此题关注的是可行性,而多次选择一个硬币类似完全背包,考虑阶段内转移优化,而设\(dp[j]\)为前i种硬币,是否组成钱为j,而\(u[j]\)表示组成钱j的最少需要钱i的个数。
于是当这个钱已经能组成,当然无需再组成,当这里没有组成,前面已经有组成的方案,比较前面的使用的该种硬币的个数,是否满足题意,即是否超越已用硬币数,之所以可行,是因为传统多重背包要求了权值最大,而使用的物品最少并不能保证权值最大,本题要求可行性,于是我们只要硬币数用的足够少,来保证组成尽可能多的硬币,于是仿照完全背包,顺序枚举
\[dp[j]|=true(!dp[j]\&\&dp[j-a_i]\&\&u[j-a_i]<c_i)
\]
边界:\(dp[0]=1\)
答案:枚举累加有1的数即可
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
using namespace std;
bool dp[100001];
int used[100001],a[101],c[101];
il void read(int&);
int main(){
int n,m;while(read(n),read(m),n&&m){
for(ri int i(1);i<=n;++i)read(a[i]);
for(ri int i(1);i<=n;++i)read(c[i]);
memset(dp,0,sizeof(dp)),dp[0]|=true;
for(ri int i(1),j;i<=n;++i){
memset(used,0,sizeof(used));
for(j=a[i];j<=m;++j)
if(used[j-a[i]]<c[i]&&dp[j-a[i]]&&!dp[j])
dp[j]|=true,used[j]=used[j-a[i]]+1;
}int ans(0);
for(ri int i(1);i<=m;++i)ans+=dp[i];
printf("%d\n",ans);
}
return 0;
}
il void read(int &x){
x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}