CF1614D1 Divan and Kostomuksha (easy version) 题解
CF1614D1 Divan and Kostomuksha (easy version)
- 思路:
发现这道题并没有什么好的构造方法,而且可以通过遍历状态空间求解,考虑 DP。
如果根据序列设计状态,会发现很难转移,而且空间也较难承受。
而题中所给值域较小,从值域方面考虑设计状态:
设 \(dp(i)\) 为因数 \(i\) 作为序列的公因数时,序列权值的最大值。
设 \(cnt_i\) 为因数 \(i\) 在序列元素中作为因数的次数,\(p_{1\ldots tot}\) 为值域内素数序列,则不难想到:
初始状态:\(dp(i)=i\times cnt_i\)
状态转移方程:\(dp(i)=\max\limits_{1\le j\le tot}(dp(i \times p_j) + i\times (cnt_i-cnt_{i\times p_j}))\)
目标状态:\(\max\limits_{cnt_i=n}(dp(i))\)
不难理解:将含有因数 \(i\) 的几个数放在最前面,其余放后面时,仅考虑 \(i\) 对于权值的贡献,即为 \(i \times cnt_i\)。
然后进行转移:当含有因数 \(i\) 的几个数放在序列后端,而前面为含因数 \(i \times p_j\) 的数,此时序列的公因数为 \(i\) ,则序列的权值要在 \(dp(i\times p_j)\) 的基础上加上 \(i \times cnt_i\) ,但 \(cnt_i\) 中包含有 \(cnt_{i \times p_j}\) 这部分,已经在前面计算过,所以要减掉。
目标状态不难理解,代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 2e7 + 5;
#define RE register
int prime[maxm],tot;
bool flag[maxm];
int n,a[maxn],w,cnt[maxm];
typedef long long ll;
ll f[maxm];
int read() {
int s = 0;
char c = getchar();
for(;!isdigit(c);c = getchar());
for(;isdigit(c);c = getchar())s = (s << 1) + (s << 3) + (c ^ '0');
return s;
}
int main() {
n = read();
for(RE int i = 1;i <= n;++ i)a[i] = read(),w = max(a[i] , w),++ cnt[a[i]];
flag[0] = flag[1] = true;
for(RE int i = 2;i <= w;++ i) {
if(!flag[i]) {
prime[++ tot] = i;
}
for(RE int j = 1;j <= tot&&prime[j] * i <= w;++ j) {
flag[i * prime[j]] = true;
if(!(i % prime[j]))break ;
}
}
for(int j = 1;j <= tot;++ j) {
for(int i = w / prime[j];i >= 1;-- i) {
cnt[i] += cnt[i * prime[j]];
}
}
for(RE int i = w;i;-- i) {
f[i] = 1ll * i * cnt[i];
for(RE int j = 1;j <= tot&&prime[j] * i <= w;++ j) {
f[i] = max(f[i] , f[i * prime[j]] + 1ll * i * (cnt[i] - cnt[i * prime[j]]));
}
}
ll ans = 0;
for(RE int i = 1;i <= w;++ i) {
if(cnt[i] == n)ans = max(ans , f[i]);
}
printf("%lld",ans);
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿