HUD 2844 coins

本文介绍了一道经典的硬币组合问题,旨在找出使用不同数量的特定面值硬币所能构成的不同价格总数。通过优化的多重背包及01背包算法,文章详细展示了如何高效地解决这一问题。

Coins

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


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
 

Source
 

Recommend
gaojie
 

Statistic |  Submit |  Discuss |  Note

题意A[i]种硬币,每种C[i]个,求解能够形成多少种小于m的不同的面值。

等价于A[i]种物品,每种C[i]个,求解能装满多少小于m的不同背包。

注意不要用对1到m的背包进行循环,这样做只会各种TLE,POJ的3秒都卡不过。

可以对物品的种类进行考虑。考虑每种物品能够去装满哪些背包。

用f数组表示背包,0表示未装满,1表示装满。

如果A[i]*C[i]>=m,则可以转化为多重背包问题。否则可以用01背包问题求解,用二进制去优化。

AC代码(内含解析):

#include<stdio.h>
#include<math.h>
#define N 100001
int a[N],c[N],b[1001];//b数组用来存放二进制优化后的物品所占空间
int m,f[N];
void zeropack(int h)
{
	int i,j,k;
for(k=0;c[h]-(int)pow(2,k)+1>0;k++);
k--;
for(i=0;i<k;i++)
b[i]=a[h]*(int)pow(2,i);
b[k]=a[h]*(c[h]-(int)pow(2,k)+1);//二进制优化
for(i=k;i>=0;i--)//对转换后的k+1种物品进行选择
{
for(j=m;j>=b[i];j--)//01背包问题,只考虑装或不装
f[j]=f[j]|f[j-b[i]];//用了一个位或运算符,可以简化代码
}
}
void compeletpack(int h)
{
int j;
for(j=a[h];j<=m;j++)//由于可以装无穷多个,则第1个价值为a[h]的包肯定能装满,而后只需考虑要不要再装一个
f[j]=f[j-a[h]]|f[j];
}
int main()
{
int i,num,n;
scanf("%d%d",&n,&m);
while(n||m)
{
num=0;
f[0]=1;
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)
scanf("%d",&c[i]);
for(i=1;i<=m;i++)//初始化
f[i]=0;
for(i=1;i<=n;i++)//对n种物品的选择
{
if(a[i]*c[i]>=m)
compeletpack(i);//对于总价值超过m的硬币,可以认为是可取无限多种
else
zeropack(i);
}
for(i=1;i<=m;i++)
num+=f[i];
printf("%d\n",num);
scanf("%d%d",&n,&m);
}
return 0;
}

对于上面的二进制优化还有一种更简洁的写法,可以不用到数组b:

for(k=1;k<=a[h]/2;k=(k<<1))

{

for(j=m;j>=k*a[h];j--)

f[j]=f[j]|f[j-k*a[h]];

}

k=a[h]+1-k;

for(j=m;j>=k*a[h];j--)

{

f[j]=f[j]|f[j-k*a[h]];

}

A了这道题,收获良多啊,慢慢消化。

### 关于分割硬币的公平分配算法 在计算机科学和数学领域,分割硬币的问题通常可以被建模为一种优化问题或动态规划问题。目标通常是找到一种方式来最小化两个集合之间的差异或者最大化某种公平性标准。 #### 动态规划解决方案 对于分割硬币使其尽可能均匀分布的情况,可以采用动态规划的方法解决此问题。假设我们有一组硬币 `coins` 和它们的价值分别为 `[c1, c2, ..., cn]`,我们需要将其分成两部分使得这两部分价值之差最小[^2]。 以下是基于动态规划的一个实现方案: ```python def min_difference_partition(coins): total_sum = sum(coins) n = len(coins) dp = [[False]*(total_sum//2 + 1) for _ in range(n+1)] # Initialize DP table for i in range(n+1): dp[i][0] = True for i in range(1, n+1): for j in range(1, total_sum//2 + 1): if coins[i-1] <= j: dp[i][j] = dp[i-1][j] or dp[i-1][j-coins[i-1]] else: dp[i][j] = dp[i-1][j] # Find the largest value that can be achieved less than half of total sum for j in range(total_sum//2, -1, -1): if dp[n][j]: return abs((total_sum - j) - j) ``` 该函数通过构建一个二维布尔数组 `dp` 来记录子集总和的可能性,并最终返回能够达到的最大接近一半总和的值,从而计算出两者间的最小差距[^3]。 #### 贪婪算法近似解法 如果追求更高效的解决方案而允许一定的误差范围,则可以考虑贪婪策略。这种方法并不总是能找到最优解,但在某些情况下表现良好。基本思路是从最大面额开始依次选取直到无法再选为止[^4]。 ```python def greedy_divide_coins(coins): coins.sort(reverse=True) group_a = [] group_b = [] for coin in coins: if sum(group_a) < sum(group_b): group_a.append(coin) else: group_b.append(coin) return (group_a, group_b), abs(sum(group_a)-sum(group_b)) ``` 尽管如此,在实际应用中需注意验证其适用性和局限性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值