题目大意
给定 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;
}