题目大意:有n种硬币,每种有不同的价值vi以及数量cnti,问这些硬币能组合出多少种金额
1<=n<=100;1<=m<=1e5;1<=vi<=1e5;1<=cnti<=1000
思路:多重背包问题,dp[i][j]表示表示前i种硬币能否拼成金额j,能为1,不能为0,第一种情况用前i-1种硬币能拼出来的,前i种也是能得,所以如果dp[i-1][j]=1,那么dp[i][j]也等于1,第二种情况,i相等时,如果当前已经得到金额j-vi,且当前硬币还有剩余,则dp[j]=1,即如果dp[j-v[i]]=1,则dp[j]=1,所以我们用一个num数组统计当前种类硬币已选取的数量,然后如果当前位置没有转移过且我们转移过来了,就令答案+1
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e5 + 5;
bool dp[N];
int v[105], num[N], cnt[105];
int main()
{
int n, m;
while (~scanf_s("%d%d", &n, &m) , (n && m))
{
int ans = 0;
for (int i = 1; i <= n; i++)
{
scanf_s("%d", &v[i]);//每种硬币的价值
}
for (int i = 1; i <= n; i++)
{
scanf_s("%d", &cnt[i]);//每种硬币的数量
}
for (int i = 1; i <= m; i++)
{
dp[i] = 0;//判断能否组成金额i
}
dp[0] = 1;
for (int i = 1; i <= n; i++)
{
for (int i = 1; i <= m; i++)
{
num[i] = 0;//每种硬币当前已用的次数
}
for (int j = v[i]; j <= m; j++)
{
if (!dp[j] && dp[j - v[i]] && num[j - v[i]] < cnt[i])
{//当前金额没有转移过,j-vi可以组成,当前硬币选择的次数小于数量
dp[j] = 1;
num[j] = num[j - v[i]] + 1;
ans++;//成功转移,答案+
}
}
}
printf("%d\n", ans);
}
return 0;
}
二进制拆分解法:将每个硬币的数量拆成2的幂,然后转化为01背包问题,从而减少物品的总数,令dp[i]表示价值为i的硬币拼成的最大金额,因为硬币的价格和重量相等,所以当价值为i的硬币拼成的最大价值也是i时,才说明能凑成当前金额,即dp[i]=i时的金额i符合要求
//#include <__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, m;
int dp[N];
int w[N], num[N];
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
while (cin>>n>>m , (n && m))
{
for (int i = 1; i <= m; i++)
{
dp[i] = 0;//dp[i]表示总价值为i的硬币能组成的最大价值
}
for (int i = 0; i < n; i++)
{
cin >> w[i];
}
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
dp[0] = 0;//没硬币时没有价值
for (int i = 0; i < n; i++)
{
if (w[i] * num[i] > m)
{//当前硬币价值*数量超过了背包容量,变成完全背包问题
for (int j = w[i]; j <= m; j++)//正推求最大价值
dp[j] = max(dp[j], dp[j - w[i]] + w[i]);//硬币的价值和重量是相等的
}
else
{
for (int k = 1; num[i] > 0; k*=2)
{//二进制拆分
int x = min(k, num[i]);//当前分出的硬币数
for (int j = m; j >= w[i] * x; j--)
{//01背包倒推
dp[j] = max(dp[j], dp[j - w[i] * x]+w[i]*x);
}
num[i] -= x;
}
}
}
int sum = 0;
for (int i = 1; i <= m; i++)
{
if (dp[i]==i)//如果价值为i的硬币拼出的最大价值等于i,则说明能凑成i
sum += 1;
}
cout << sum << endl;
}
return 0;
}