【codeforces757 Div2 D1】Divan and Kostomuksha(1614D1)题解

题目大意

给定 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] xcnt[x];
然后假设 g c d gcd gcd下降到了 y y y,那么可以推出 y ∣ x y | x yx,即$ (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 yx),那么增加的结果改怎么计算呢? 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] (xy)cnt[x]
  • 然后我们再把 c n t [ x ] cnt[x] cnt[x]中, g c d gcd gcd z ( x ∣ z ) z(x | z) z(xz)的数放在前面,再次计算增加的数。

一直重复上述过程。

当然 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]+(ji)cnt[j]),其中 i ∣ j i | j ij

代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值