洛谷P2851 [USACO06DEC]The Fewest Coins G 题解

洛谷P2851 [USACO06DEC]The Fewest Coins G 题解

题目链接:P2851 [USACO06DEC]The Fewest Coins G

题意

Farmer John has gone to town to buy some farm supplies. Being a very efficient man, he always pays for his goods in such a way that the smallest number of coins changes hands, i.e., the number of coins he uses to pay plus the number of coins he receives in change is minimized. Help him to determine what this minimum number is.

FJ wants to buy T (1 ≤ T ≤ 10,000) cents of supplies. The currency system has N (1 ≤ N ≤ 100) different coins, with values V1, V2, …, VN (1 ≤ Vi ≤ 120). Farmer John is carrying C1 coins of value V1, C2 coins of value V2, …, and CN coins of value VN (0 ≤ Ci ≤ 10,000). The shopkeeper has an unlimited supply of all the coins, and always makes change in the most efficient manner (although Farmer John must be sure to pay in a way that makes it possible to make the correct change).

农夫John想到镇上买些补给。为了高效地完成任务,他想使硬币的转手次数最少。即使他交付的硬币数与找零得到的的硬币数最少。

John想要买价值为T的东西。有N(1<=n<=100)种不同的货币参与流通,面值分别为V1,V2…Vn (1<=Vi<=120)。John有Ci个面值为Vi的硬币(0<=Ci<=10000)。

我们假设店主有无限多的硬币, 并总按最优方案找零。注意无解输出-1。

不妨把题目转化成下图的形式

在这里插入图片描述

f [ j ] f[j] f[j] 表示付钱的总额为 j j j 时所需的最小硬币数

显然这是一个多重背包,直接求解即可

然后设 g [ j ] g[j] g[j] 表示找零的总额为 j j j 时的最小硬币数

显然这是一个完全背包,直接求解即可

然后将其合并就好了

本题的关键在于背包容量的上界如何确定

结论 f f f 背包容量上界为 T + 2 V max ⁡ 2 T+2V_{\max}^2 T+2Vmax2 ,即 g g g 背包容量上界为 2 V max ⁡ 2 2V_{\max}^2 2Vmax2

证明:翻译+适当修改自 prove ,有错欢迎指出

方便起见,下文中将使用 V V V 表示 V max ⁡ V_{\max} Vmax

引理:对于背包容量 m m m V V V 意义下的每个剩余类,我们都可以使用总额不超过 V 2 V^2 V2 的若干硬币来表示。

证明:考虑反证法。

设某种硬币组合的总和 S ≥ V 2 S \ge V^2 SV2 并且不可能产生总和为 S − k × V , k ∈ N S-k\times V,k \in \mathbb{N} Sk×V,kN 的硬币组合

令这种硬币组合形成的多重集为 { x 1 , x 2 , … , x k } \{x_1,x_2,\dots,x_k\} {x1,x2,,xk}

因为总和至少为 V 2 V^2 V2 ,所以 k ≥ V k\ge V kV

考虑对这个多重集做前缀和,也就是把多重集的每个元素以某种方式排列,然后计算 x 1 , x 1 + x 2 , x 1 + x 2 + x 3 , … x_1,x_1+x_2,x_1+x_2+x_3,\dots x1,x1+x2,x1+x2+x3,

s n = ∑ 1 ≤ i ≤ n x i s_n = \sum_{1\le i \le n} x_i sn=1inxi

根据 S S S 的定义可知, s i s_i si 在模 V V V 意义下不可能为 0 0 0

因此 s i s_i si 只有 V − 1 V-1 V1 种可能性

根据鸽巢原理,至少存在一对 i , j ( 1 ≤ i < j ≤ k ) i,j(1 \le i < j \le k) i,j(1i<jk) 使得
s j − s i = d ( m o d V ) s_j-s_i = d \pmod{V} sjsi=d(modV)
也就是排列后,区间 [ i , j ] [i,j] [i,j] 的和为 V V V 的倍数

因此我们可以考虑删除一些能被 V V V 整除的子集,产生总和为 S − k × V , k ∈ N S-k\times V,k \in \mathbb{N} Sk×V,kN 的多重集

显然这与我们的假设相矛盾

因此对于背包容量 m m m V V V 意义下的每个剩余类,我们都可以使用总额不超过 V 2 V^2 V2 的若干硬币来表示。

同时,我们还证明了对于所有总和大于 V 2 V^2 V2 的多重集,至少有一个 V V V 硬币

现在我们知道,对于所有的目标总额 S ≥ 2 V 2 S \ge 2V^2 S2V2 ,使得 S   m o d   V = d S \bmod V = d SmodV=d ,由于我们一定会选择更优的多重集使得 ∑ x i   m o d   V = d \sum x_i \bmod V = d ximodV=d ,于是在 d d d 上添加 V V V 类型的硬币直到达到 S S S (这里用到了贪心的思想)

我们回到问题,假设 Farmer John 得到 X X X 元的找零,且 X ≥ 2 V 2 + v X \ge 2V^2 + v X2V2+v

还是刚才那张图

在这里插入图片描述

根据 X X X 的定义,右边黑色的一段至少为 2 V 2 2V^2 2V2 (这里的 v v v 表示中间那个被 T T T “切成”两半的块的超过 T T T 的部分,这样假设是为了忽略 T T T 的干扰)

在黑色块中,总和至少为 V 2 V^2 V2 (实际上是 2 V 2 2V^2 2V2 ,但只考虑 V 2 V^2 V2 ),因此我们证明了存在一个黑色块子集可以被 V V V 整除

此外,找零的总额大于等于 2 V 2 2V^2 2V2 ,故右边蓝色的一段中, 所有的 V V V 中至少有 V 2 V^2 V2 (原文有点拗口,其实意思是:至少用了 V V V V V V

因此我们用蓝色块去消掉了黑色块,而枚举这样的情况并没有意义,它等价于消掉的那种情况

而上面提到了一个更优的方法

因此最优解的找零最多不大于 2 V 2 2V^2 2V2

也就是 f f f 背包容量上界为 T + 2 V max ⁡ 2 T+2V_{\max}^2 T+2Vmax2 ,即 g g g 背包容量上界为 2 V max ⁡ 2 2V_{\max}^2 2Vmax2

代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(105)
#define M (int)(1e4+15)
#define V (int)(120)
int n,m;
int f[M+2*V*V],g[2*V*V];
int v[N],c[N],sum,mx;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> m;
    for(int i=1; i<=n; i++)
        cin >> v[i];
    for(int i=1; i<=n; i++)
    {
        cin >> c[i];
        sum+=c[i]*v[i];
        mx=max(mx,v[i]*v[i]);
    }mx<<=1;
    if(sum<m)return cout << -1,0;
    memset(f,0x3f,sizeof(f));
    memset(g,0x3f,sizeof(g));
    f[0]=g[0]=0;
    for(int i=1; i<=n; i++)
        for(int j=v[i]; j<=mx; j++)
            g[j]=min(g[j],g[j-v[i]]+1);
    mx+=m;
    for(int i=1; i<=n; i++)
    {
        for(int t=1; t<=c[i]; t<<=1)
        {
            for(int j=mx; j>=t*v[i]; j--)
                f[j]=min(f[j],f[j-t*v[i]]+t);
            c[i]-=t;
        }
        if(c[i])
            for(int j=mx; j>=c[i]*v[i]; j--)
                f[j]=min(f[j],f[j-c[i]*v[i]]+c[i]);
    }
    int res=INF;
    for(int i=m; i<=mx; i++)
        res=min(res,f[i]+g[i-m]);
    cout << ((res==INF)?(-1):res) << endl;
    return 0;
}

转载请说明出处

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值