挑战程序设计竞赛:硬币问题

题目大意

在这里插入图片描述

解题思路

这道硬币问题比较特殊,因为所有的币值都是更小币值的倍数(这句话存疑,以后有时间证明试试,个人感觉是这样),因此可以使用贪心算法求解。
(1)贪心思路:

  • 贪心策略: 尽量选择现有更大面值的硬币去支付还需要支付的钱。
  • 复杂度: O ( n ) O(n) O(n)

但是如果换一种硬币面额,比如现在有 { 2 , 3 , 4 } \{2,3,4\} {2,3,4}, 我们要组成面额为 5 5 5的硬币。如果先选了4,就不行了。基于此,我们提出动态规划思想。我们将其转换为多重背包问题。

(2)动态规划思路:
首先我们需要采用多重背包将数量进行二进制压缩,我们使用:(1) v a l u e [ i ] value[i] value[i]表示压缩后的第 i i i个压缩硬币的价值.(2): w e i g h t [ i ] weight[i] weight[i]:表示第 i i i个压缩硬币实际代表的硬币数。如下图所示:
在这里插入图片描述
基于此,我们有:

  • 定义 d p [ i ] [ j ] dp[i][j] dp[i][j]: 前 i i i个硬币组成面额为 j j j的钱需要的最小硬币数量。如果为 i n f inf inf,则不能组成。
  • 目标 d p [ t o t a l ] [ A ] dp[total][A] dp[total][A]: 即所有硬币组成目标面额 A A A所需的最小硬币数量。
  • 状态转移: 对于第 i i i个压缩硬币,我们可以有选和不选两种方式。因此:
    d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v a l u e [ i ] ] + w e i g h t [ i ] ) dp[i][j] = min(dp[i-1][j], dp[i-1][j-value[i]] + weight[i]) dp[i][j]=min(dp[i1][j],dp[i1][jvalue[i]]+weight[i])
  • 转移策略: 按 i i i从小到大更新。
  • 初始状态: d p [ 0 ] [ ∗ ] = i n f , d p [ 0 ] [ 0 ] = 0 dp[0][*] = inf, dp[0][0] = 0 dp[0][]=inf,dp[0][0]=0.
  • 复杂度: O ( n l o g ( C i ) A ) O(nlog(C_i)A) O(nlog(Ci)A):其中 n l o g ( C i ) nlog(C_i) nlog(Ci)表示压缩后的物品个数, A A A最大的表示价值。
  • 缺点:由于这道题A太大,因此无法用背包思想求解。

代码

  • 贪心策略:
#include<iostream>
using namespace std;

int main()
{
    int A;
    int value[6] = {1, 5, 10, 50, 100, 500};
    int num[6];
    for(int i=0; i<6; i++)
        cin >> num[i];
    cin >> A;
    int ans = 0;
    for(int i=5; i>=0; i--)
    {
        int per_num = min(num[i], A/value[i]);
        ans += per_num;
        A -= per_num * value[i];
        if(A == 0)
            break;
   }
   cout << ans << endl;
   return 0;
}

  • 动态规划: 由于最大价值 A A A太大,因此这里按照最大价值仅有 1000 1000 1000编码。
#include<iostream>
using namespace std;

const int MAX = 30*5+5;
const int MAXM = 1000;
const int inf = 1 << 20;
int value[MAX];
int weight[MAX];
int dp[2][1000];
int main()
{
    int C;
    int cnt = 1;
    int a[6] = {1,5,10,50,100,500};
    for(int i=0; i<6; i++)
    {
        cin >> C;
        int now_num = 1;
		
		// --------------注意多重背包的压缩方法-----------------
        while((C-now_num) >= 0)
        {
            value[cnt] = now_num * a[i];
            weight[cnt] = now_num;
            cnt++;
            C -= now_num;
            now_num <<= 1;
        }
        if(C)
        {
            value[cnt] = C * a[i];
            weight[cnt] = C;
            cnt++;
        }
        ------------------------------------------------------
    }
    int A;
    cin >> A;
    for(int i=0; i<=A; i++)
        dp[0][i] = inf;
    dp[0][0] = 0;
    for(int i=1; i<=cnt-1; i++)
    {
        for(int j=0; j<=A; j++)
        {
            if(j >= value[i])
                dp[i%2][j] = min(dp[1-i%2][j], dp[1-i%2][j-value[i]] + weight[i]);
            else
                dp[i%2][j] = dp[1-i%2][j];
        }
    }
    cout << dp[1-cnt%2][A] << endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值