[CF585E] Present for Vitalik the Philatelist

[题目链接]

https://codeforces.com/contest/585/problem/E

[题解]

\(s_{n}\) 表示最大公约数恰好为 \(n\) 的集合个数 , \(f_{n}\) 表示与 \(n\) 互质的数个数。

那么答案为 \(\sum{s_{i}f_{i}}\)

考虑计算 \(f\) , 令 \(c_{i}\) 表示 \(i\) 的出现次数。

那么有 \(f_{n} = \sum_{i}{[gcd(i , n) = 1]c_{i}} = \sum_{i}{c_{i}}\sum_{d|i,d|n}{\mu(d)} = \sum_{d|n}{\mu(d)}\sum_{d|i}{c_{i}}\)

不妨令 \(g_{k} = \sum_{k|i}{c_{i}} , g'_{k} = \mu(k)g(k)\) , 那么 \(f_{n} = \sum_{d|n}{g'_{d}}\)

再考虑求 \(s_{i}\) , 不妨令 \(s'_{i}\) 为最大公约数为 \(i\) 倍数的方案数 , 显然 \(s'_{i} = 2 ^ {g_{i}} - 1\)

又有 \(s'_{i} = \sum_{i|d}{s_{d}}\)

这两个式子都是 \(a_{k} = \sum_{d|k}{b_{k}}\)\(a_{k} = \sum_{k|d}{b_{k}}\) 的形式 , 可以求狄利克雷前缀和 (第二个式子实质上是求逆)。

时间复杂度 : \(O(NlogN)\)

[代码]

#include<bits/stdc++.h>

using namespace std;

#define rep(i , l , r) for (int i = (l); i < (r); ++i)

typedef long long LL;

const int MN = 5e5 + 5 , MM = 1e7 + 5 , mod = 1e9 + 7;

int n , lim , s[MM] , two[MN] , a[MN] , tot[MM] , g[MM] , pr[MM / 10] , cnt , mu[MM] , vis[MM];

inline void inc(int &x , int y) {
	 x = x + y < mod ? x + y : x + y - mod;
}
inline void dec(int &x , int y) {
	 x = x - y >= 0 ? x - y : x - y + mod;
}
inline void prework(int n) {
	 mu[1] = 1;
	 for (int i = 2; i <= n; ++i) {
	 		if (!vis[i]) pr[++cnt] = i , mu[i] = -1;
	 		for (int j = 1; j <= cnt && i * pr[j] <= n; ++j) {
	 				vis[i * pr[j]] = 1;
					if (i % pr[j] == 0) {
							mu[i * pr[j]] = 0;
							break;
					}	else mu[i * pr[j]] = -mu[i];
			}  
	 }
}
int main() {
		
		scanf("%d" , &n); two[0] = 1;
		for (int i = 1; i <= 500000; ++i) two[i] = 2ll * two[i - 1] % mod;
		for (int i = 1; i <= n; ++i) {
				scanf("%d" , &a[i]);
				++tot[a[i]];
		}
		lim = *max_element(a + 1 , a + 1 + n); prework(lim);
		for (int i = 1; i <= cnt; ++i)
		for (int j = lim / pr[i]; j; --j)
				tot[j] += tot[j * pr[i]];
		for (int i = 1; i <= lim; ++i) g[i] = (1ll * tot[i] * mu[i] % mod + mod) % mod;
		for (int i = 1; i <= cnt; ++i)
		for (int j = 1; j * pr[i] <= lim; ++j)
				inc(g[j * pr[i]] , g[j]);
		for (int i = 1; i <= lim; ++i) s[i] = two[tot[i]] - 1;
		for (int i = cnt; i; --i)
		for (int j = 1; j * pr[i] <= lim; ++j)
				dec(s[j] , s[j * pr[i]]);
		int ans = 0;
		for (int i = lim; i > 1; --i) inc(ans , 1ll * s[i] * g[i] % mod);
		printf("%d\n" , ans);
	  return 0;
}
posted @ 2021-01-24 16:54  evenbao  阅读(84)  评论(0编辑  收藏  举报