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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maxwei_wzj/article/details/83591768

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

#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;
}

猜你喜欢

转载自blog.csdn.net/Maxwei_wzj/article/details/83591768