题目大意
给定
n
n
n个正整数
a
1
,
2...
n
a_{1,2...n}
a1,2...n。
可以任意改变顺序。
求
g
c
d
(
a
1
)
+
g
c
d
(
a
1
,
a
2
)
+
.
.
.
+
g
c
d
(
a
1
,
a
2
,
.
.
.
,
a
n
)
gcd(a_1) + gcd(a_1,a_2)+...+gcd(a_1,a_2,...,a_n)
gcd(a1)+gcd(a1,a2)+...+gcd(a1,a2,...,an)的最大值。
思路
直接求 g c d gcd gcd时间复杂度肯定会炸。所以我们用 c n t [ j ] cnt[j] cnt[j]表示因数包含 j j j的数的个数,用这样的计算方式代替求 g c d gcd gcd。
借助 c n t cnt cnt数组计算
这个前缀
g
c
d
gcd
gcd大小会逐渐下降的。
假设最开始
g
c
d
gcd
gcd为
x
x
x,那么对答案的贡献就是
x
∗
c
n
t
[
x
]
x * cnt[x]
x∗cnt[x];
然后假设
g
c
d
gcd
gcd下降到了
y
y
y,那么可以推出
y
∣
x
y | x
y∣x,即$ (x \mod y = 0)
,
我
们
来
计
算
这
一
部
分
数
的
贡
献
,
因
为
实
际
上
,我们来计算这一部分数的贡献, 因为实际上
,我们来计算这一部分数的贡献,因为实际上cnt[y]
包
含
了
包含了
包含了cnt[x]
,
而
,而
,而cnt[x]
已
经
被
计
算
过
了
,
所
以
要
减
去
它
们
,
为
已经被计算过了, 所以要减去它们,为
已经被计算过了,所以要减去它们,为y * (cnt[y]-cnt[x])$。
DP
上述分析并不能解出本题,但帮我们了解了递推关系。
最优解法肯定与这种思路类似:把尽量大的,且与后面的数gcd大的数尽量放在前面。
但是这么想是很难去推的,所以我们应该逆着来推:
假设最开始前面的数的
g
c
d
gcd
gcd就是
1
1
1,那么初始结果就是
n
n
n,然后我们不断地把gcd大的数放在前面,计算这么做 增大的部分。
- 假设最开始所有数的 g c d gcd gcd均为 y y y(实际上最开始我们是从 1 1 1开始的),那么初始结果就是 c n t [ y ] ∗ y cnt[y] * y cnt[y]∗y。
- 然后我们把所有 g c d gcd gcd为 x x x的数放在前面( y ∣ x y|x y∣x),那么增加的结果改怎么计算呢? c n t [ x ] cnt[x] cnt[x]中的每个数在计算 c n t [ y ] cnt[y] cnt[y]的贡献时,已经被计算了一次,所以我们应该计算它门多出来的部分,即 ( x − y ) ∗ c n t [ x ] (x - y) * cnt[x] (x−y)∗cnt[x]。
- 然后我们再把 c n t [ x ] cnt[x] cnt[x]中, g c d gcd gcd为 z ( x ∣ z ) z(x | z) z(x∣z)的数放在前面,再次计算增加的数。
一直重复上述过程。
当然
g
c
d
gcd
gcd可能会有很多个,我们并不能确定把哪个放在前面是最优的,所以此时我们就要用
D
P
DP
DP的做法了。
那么我们改动一下上述推算结果,状态转移方程就能写出来了
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
i
]
+
(
j
−
i
)
∗
c
n
t
[
j
]
)
dp[j] = max(dp[j], dp[i] + (j - i) * cnt[j])
dp[j]=max(dp[j],dp[i]+(j−i)∗cnt[j]),其中
i
∣
j
i | j
i∣j。
代码
#include <cstdio>
#include <iostream>
using namespace std;
const int maxN = 5e6;
int n;
long long dp[maxN + 10], cnt[maxN + 10];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
cnt[x]++;
}
for(int i = 1; i <= maxN; ++i)
for(int j = 2 * i; j <= maxN; j += i)
cnt[i] += cnt[j];
dp[1] = n;
long long ans = 0;
for(int i = 1; i <= maxN; ++i)
for(int j = 2 * i; j <= maxN; j += i)
dp[j] = max(dp[j], (j - i) * cnt[j] + dp[i]);
for(int i = 1; i <= maxN; ++i)
ans = max(ans, dp[i]);
printf("%lld\n", ans);
return 0;
}