【BZOJ3233】找硬币(AHOI2013)-DP+数论

测试地址:找硬币
做法: 本题需要用到DP+数论。
假设我们有了构造出了一个合法硬币序列 x x x,怎么计算最少需要使用的硬币数量?显然,因为 x k x_k xk x k − 1 x_{k-1} xk1的倍数,能用大的就应该用大的,那么对于最大的币值 x k x_k xk,应该要使用 ⌊ a i x k ⌋ \lfloor\frac{a_i}{x_k}\rfloor xkai个,于是还剩下 a i % x k a_i\%x_k ai%xk需要支付,于是对于第二大的币值 x k − 1 x_{k-1} xk1需要使用 ⌊ a i % x k x k − 1 ⌋ \lfloor\frac{a_i\%x_k}{x_{k-1}}\rfloor xk1ai%xk个,于是还剩下 a i % x k − 1 a_i\% x_{k-1} ai%xk1需要支付…以此类推。于是我们得到答案:
a n s = ∑ i = 1 n ( ⌊ a i x k ⌋ + ∑ j = 1 k − 1 ⌊ a i % x j + 1 x j ⌋ ) ans=\sum_{i=1}^n(\lfloor\frac{a_i}{x_k}\rfloor+\sum_{j=1}^{k-1}\lfloor\frac{a_i\%x_{j+1}}{x_j}\rfloor) ans=i=1n(xkai+j=1k1xjai%xj+1)
我们发现 x j x_j xj之间互相产生贡献,会且只会在相邻的 x j x_j xj x j + 1 x_{j+1} xj+1之间,以及最后再补上一个 ⌊ a i x k ⌋ \lfloor\frac{a_i}{x_k}\rfloor xkai,也就是说 x j x_j xj的选取是一个可以多阶段决策的问题,也就可以用动态规划解决了。
为了方便,我们先把 ⌊ a i x k ⌋ \lfloor\frac{a_i}{x_k}\rfloor xkai那个部分略掉(因为可以 O ( n ⋅ max ⁡ a i ) O(n\cdot \max a_i) O(nmaxai)算出),令 f ( p ) f(p) f(p) x k = p x_k=p xk=p a n s ans ans的最小值,那么有状态转移方程:
f ( x ) = min ⁡ { f ( y ) + ∑ i = 1 n ⌊ a i % x y ⌋ } ( y ∣ x ) f(x)=\min\{f(y)+\sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor\}(y|x) f(x)=min{f(y)+i=1nyai%x}(yx)
边界为 f ( 1 ) = 0 f(1)=0 f(1)=0。这个东西每次转移都暴力计算是 O ( n ) O(n) O(n)的,而鉴于从 f ( p ) f(p) f(p)可以转移到 p p p的所有的倍数,因此转移次数是一个调和级数,也就是 O ( max ⁡ a i ⋅ log ⁡ ( max ⁡ a i ) ) O(\max a_i\cdot \log(\max a_i)) O(maxailog(maxai))次转移,那么总的时间复杂度为 O ( n ⋅ max ⁡ a i ⋅ log ⁡ ( max ⁡ a i ) ) O(n\cdot \max a_i\cdot \log(\max a_i)) O(nmaxailog(maxai)),爆炸的可能性很大(我没试过,但应该会挂)。
于是我们需要找到 O ( 1 ) O(1) O(1)转移的方法,唯一的方式只有预处理出一部分答案,而上面那个 ∑ i = 1 n ⌊ a i % x y ⌋ \sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor i=1nyai%x实在有点纠结,我们考虑怎么把 ⌊ a % x y ⌋ \lfloor\frac{a\%x}{y}\rfloor ya%x转化成更好算的式子。
直觉上,我们感觉到 ⌊ a % x y ⌋ = ⌊ a y ⌋ % x y \lfloor\frac{a\%x}{y}\rfloor=\lfloor\frac{a}{y}\rfloor\%\frac{x}{y} ya%x=ya%yx,注意此处 y ∣ x y|x yx,这是一个非常重要的性质。简单证明如下:
根据等式的左边,可以令 a = k 1 x + r 1 ( 0 ≤ r 1 &lt; x ) a=k_1x+r_1(0\le r_1&lt;x) a=k1x+r1(0r1<x),则 r 1 = a % x r_1=a\% x r1=a%x,而令 r 1 = k 2 y + r 2 ( 0 ≤ r 2 &lt; y ) r_1=k_2y+r_2(0\le r_2&lt;y) r1=k2y+r2(0r2<y),则 k 2 = ⌊ a % x y ⌋ k_2=\lfloor\frac{a\%x}{y}\rfloor k2=ya%x
a = k 1 x + k 2 y + r 2 a=k_1x+k_2y+r_2 a=k1x+k2y+r2代入等式的右边,那么 ⌊ a y ⌋ = k 1 ⋅ x y + k 2 \lfloor\frac{a}{y}\rfloor=k_1\cdot \frac{x}{y}+k_2 ya=k1yx+k2,而 k 2 = ⌊ r 1 y ⌋ , r 1 &lt; x k_2=\lfloor\frac{r_1}{y}\rfloor,r_1&lt;x k2=yr1,r1<x,于是 k 2 &lt; x y k_2&lt;\frac{x}{y} k2<yx,因此 k 2 k_2 k2 = ⌊ a y ⌋ % x y =\lfloor\frac{a}{y}\rfloor\%\frac{x}{y} =ya%yx,等式两边都等于 k 2 k_2 k2,所以等式成立。
然后 ⌊ a y ⌋ % x y = ⌊ a y ⌋ − ⌊ ⌊ a y ⌋ x y ⌋ ⋅ x y \lfloor\frac{a}{y}\rfloor\%\frac{x}{y}=\lfloor\frac{a}{y}\rfloor-\Big\lfloor\frac{\lfloor\frac{a}{y}\rfloor}{\frac{x}{y}}\Big\rfloor\cdot \frac{x}{y} ya%yx=yayxyayx,因为 x y \frac{x}{y} yx是正整数,根据一个用过很多次我不想再证的结论 ⌊ ⌊ a m ⌋ n ⌋ = ⌊ a m n ⌋ \Big\lfloor\frac{\lfloor\frac{a}{m}\rfloor}{n}\Big\rfloor=\lfloor\frac{a}{mn}\rfloor nma=mna,上式就可以写成 ⌊ a y ⌋ − ⌊ a x ⌋ ⋅ x y \lfloor\frac{a}{y}\rfloor-\lfloor\frac{a}{x}\rfloor\cdot \frac{x}{y} yaxayx。有了这一结论,带回一开始的式子中去,则有:
∑ i = 1 n ⌊ a i % x y ⌋ = ∑ i = 1 n ( ⌊ a i y ⌋ − ⌊ a i x ⌋ ⋅ x y ) = ( ∑ i = 1 n ⌊ a i y ⌋ ) − x y ⋅ ( ∑ i = 1 n ⌊ a i x ⌋ ) \sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor=\sum_{i=1}^n(\lfloor\frac{a_i}{y}\rfloor-\lfloor\frac{a_i}{x}\rfloor\cdot \frac{x}{y})=(\sum_{i=1}^n\lfloor\frac{a_i}{y}\rfloor)-\frac{x}{y}\cdot(\sum_{i=1}^n\lfloor\frac{a_i}{x}\rfloor) i=1nyai%x=i=1n(yaixaiyx)=(i=1nyai)yx(i=1nxai)
g ( x ) = ∑ i = 1 n ⌊ a i x ⌋ g(x)=\sum_{i=1}^n\lfloor\frac{a_i}{x}\rfloor g(x)=i=1nxai,则状态转移方程就可以写成:
f ( x ) = min ⁡ { f ( y ) + g ( y ) − x y ⋅ g ( x ) } ( y ∣ x ) f(x)=\min\{f(y)+g(y)-\frac{x}{y}\cdot g(x)\}(y|x) f(x)=min{f(y)+g(y)yxg(x)}(yx)
g ( x ) g(x) g(x)可以 O ( n ⋅ max ⁡ a i ) O(n\cdot \max a_i) O(nmaxai)预处理,于是转移就是 O ( 1 ) O(1) O(1)的了,总的复杂度就是 O ( n ⋅ max ⁡ a i + max ⁡ a i ⋅ log ⁡ ( max ⁡ a i ) ) O(n\cdot \max a_i+\max a_i\cdot \log(\max a_i)) O(nmaxai+maxailog(maxai)),可以轻易地通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,a[55],maxa=0,tot[100010],f[100010],ans=inf;
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxa=max(maxa,a[i]);
    }
     
    for(int i=1;i<=maxa;i++)
    {
        f[i]=inf;
        tot[i]=0;
        for(int j=1;j<=n;j++)
            tot[i]+=a[j]/i;
    }
    f[1]=0;
     
    for(int i=1;i<=maxa;i++)
    {
        for(int k=2;i*k<=maxa;k++)
            f[i*k]=min(f[i*k],f[i]+tot[i]-tot[i*k]*k);
        ans=min(ans,f[i]+tot[i]);
    }
    printf("%d",ans);
     
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值