版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maxwei_wzj/article/details/83591768
测试地址:找硬币
做法: 本题需要用到DP+数论。
假设我们有了构造出了一个合法硬币序列
x,怎么计算最少需要使用的硬币数量?显然,因为
xk为
xk−1的倍数,能用大的就应该用大的,那么对于最大的币值
xk,应该要使用
⌊xkai⌋个,于是还剩下
ai%xk需要支付,于是对于第二大的币值
xk−1需要使用
⌊xk−1ai%xk⌋个,于是还剩下
ai%xk−1需要支付…以此类推。于是我们得到答案:
ans=∑i=1n(⌊xkai⌋+∑j=1k−1⌊xjai%xj+1⌋)
我们发现
xj之间互相产生贡献,会且只会在相邻的
xj和
xj+1之间,以及最后再补上一个
⌊xkai⌋,也就是说
xj的选取是一个可以多阶段决策的问题,也就可以用动态规划解决了。
为了方便,我们先把
⌊xkai⌋那个部分略掉(因为可以
O(n⋅maxai)算出),令
f(p)为
xk=p时
ans的最小值,那么有状态转移方程:
f(x)=min{f(y)+∑i=1n⌊yai%x⌋}(y∣x)
边界为
f(1)=0。这个东西每次转移都暴力计算是
O(n)的,而鉴于从
f(p)可以转移到
p的所有的倍数,因此转移次数是一个调和级数,也就是
O(maxai⋅log(maxai))次转移,那么总的时间复杂度为
O(n⋅maxai⋅log(maxai)),爆炸的可能性很大(我没试过,但应该会挂)。
于是我们需要找到
O(1)转移的方法,唯一的方式只有预处理出一部分答案,而上面那个
∑i=1n⌊yai%x⌋实在有点纠结,我们考虑怎么把
⌊ya%x⌋转化成更好算的式子。
直觉上,我们感觉到
⌊ya%x⌋=⌊ya⌋%yx,注意此处
y∣x,这是一个非常重要的性质。简单证明如下:
根据等式的左边,可以令
a=k1x+r1(0≤r1<x),则
r1=a%x,而令
r1=k2y+r2(0≤r2<y),则
k2=⌊ya%x⌋。
将
a=k1x+k2y+r2代入等式的右边,那么
⌊ya⌋=k1⋅yx+k2,而
k2=⌊yr1⌋,r1<x,于是
k2<yx,因此
k2也
=⌊ya⌋%yx,等式两边都等于
k2,所以等式成立。
然后
⌊ya⌋%yx=⌊ya⌋−⌊yx⌊ya⌋⌋⋅yx,因为
yx是正整数,根据一个用过很多次我不想再证的结论
⌊n⌊ma⌋⌋=⌊mna⌋,上式就可以写成
⌊ya⌋−⌊xa⌋⋅yx。有了这一结论,带回一开始的式子中去,则有:
∑i=1n⌊yai%x⌋=∑i=1n(⌊yai⌋−⌊xai⌋⋅yx)=(∑i=1n⌊yai⌋)−yx⋅(∑i=1n⌊xai⌋)
令
g(x)=∑i=1n⌊xai⌋,则状态转移方程就可以写成:
f(x)=min{f(y)+g(y)−yx⋅g(x)}(y∣x)
而
g(x)可以
O(n⋅maxai)预处理,于是转移就是
O(1)的了,总的复杂度就是
O(n⋅maxai+maxai⋅log(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;
}