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;
}

完结撒花✿✿ヽ(°▽°)ノ✿

posted @ 2022-01-24 18:33  ImALAS  阅读(68)  评论(0编辑  收藏  举报