测试地址:找硬币
做法: 本题需要用到DP+数论。
假设我们有了构造出了一个合法硬币序列
x
x
x,怎么计算最少需要使用的硬币数量?显然,因为
x
k
x_k
xk为
x
k
−
1
x_{k-1}
xk−1的倍数,能用大的就应该用大的,那么对于最大的币值
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}
xk−1需要使用
⌊
a
i
%
x
k
x
k
−
1
⌋
\lfloor\frac{a_i\%x_k}{x_{k-1}}\rfloor
⌊xk−1ai%xk⌋个,于是还剩下
a
i
%
x
k
−
1
a_i\% x_{k-1}
ai%xk−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)
ans=∑i=1n(⌊xkai⌋+∑j=1k−1⌊xjai%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(n⋅maxai)算出),令
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=1n⌊yai%x⌋}(y∣x)
边界为
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(maxai⋅log(maxai))次转移,那么总的时间复杂度为
O
(
n
⋅
max
a
i
⋅
log
(
max
a
i
)
)
O(n\cdot \max a_i\cdot \log(\max a_i))
O(n⋅maxai⋅log(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=1n⌊yai%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
y∣x,这是一个非常重要的性质。简单证明如下:
根据等式的左边,可以令
a
=
k
1
x
+
r
1
(
0
≤
r
1
<
x
)
a=k_1x+r_1(0\le r_1<x)
a=k1x+r1(0≤r1<x),则
r
1
=
a
%
x
r_1=a\% x
r1=a%x,而令
r
1
=
k
2
y
+
r
2
(
0
≤
r
2
<
y
)
r_1=k_2y+r_2(0\le r_2<y)
r1=k2y+r2(0≤r2<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⌋=k1⋅yx+k2,而
k
2
=
⌊
r
1
y
⌋
,
r
1
<
x
k_2=\lfloor\frac{r_1}{y}\rfloor,r_1<x
k2=⌊yr1⌋,r1<x,于是
k
2
<
x
y
k_2<\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=⌊ya⌋−⌊yx⌊ya⌋⌋⋅yx,因为
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
⌊n⌊ma⌋⌋=⌊mna⌋,上式就可以写成
⌊
a
y
⌋
−
⌊
a
x
⌋
⋅
x
y
\lfloor\frac{a}{y}\rfloor-\lfloor\frac{a}{x}\rfloor\cdot \frac{x}{y}
⌊ya⌋−⌊xa⌋⋅yx。有了这一结论,带回一开始的式子中去,则有:
∑
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=1n⌊yai%x⌋=∑i=1n(⌊yai⌋−⌊xai⌋⋅yx)=(∑i=1n⌊yai⌋)−yx⋅(∑i=1n⌊xai⌋)
令
g
(
x
)
=
∑
i
=
1
n
⌊
a
i
x
⌋
g(x)=\sum_{i=1}^n\lfloor\frac{a_i}{x}\rfloor
g(x)=∑i=1n⌊xai⌋,则状态转移方程就可以写成:
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)−yx⋅g(x)}(y∣x)
而
g
(
x
)
g(x)
g(x)可以
O
(
n
⋅
max
a
i
)
O(n\cdot \max a_i)
O(n⋅maxai)预处理,于是转移就是
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(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;
}