Coins hdu2844

23 篇文章 0 订阅

Problem - 2844

题目大意:有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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值