牛客挑战44C: 有用的数
题意
定义前缀 为 且一个数 对其前缀 有贡献当且仅当
特别的, 不视为对其前缀 有贡献
现在你需要求出 内所有对其前缀 有贡献的数的个数
分析
考虑一个数怎么样才会对前缀 造成影响
会发现这个数一定会是素数幂,就是说,当一个数可以表示为 ,那么
所以,这道题就转化为了在 中有多少个素数幂
进一步考虑:
- 当 时, 素数 的贡献为
- 当 时,素数 的贡献为
所以在根号范围内的答案是可以线性筛硬算的,那么在根号范围外的就是求素数个数的问题了
那么我们来聊一聊这个根号外的素数个数该怎么求呢
首先,声明几个记号:
- 表示第 个素数
- 表示 范围内的素数个数
- 表示不大于 且不能整除任何一个 的自然数个数
那么显然有:
给定一个自然数 ,如果 ,那么有:
这个方法大概可以计算 数量级的素数个数
但是有一种更快的处理方式,对于实数 ,和自然数 和 , 定义 表示不大于 ,且恰好有 个大于 的素因子的整数个数,更进一步,设定: ,那么有:
这个和 实际上只有有限个非零的项。设 为一个整数,使得 ,并设 。那么当 时, 且 。因此有:
的计算方式可以用这种方法来获得:
这样,我们就可以在 的时间复杂度内完成 的素数个数的计算
然后就可以用线性筛求出根号内的素数幂 的贡献 (c=1的贡献在上一步中已经计算过了)
,加在一起即可
Code
#include <cstdio> #include <cmath> typedef long long ll; const int maxn = 1e5 + 10; const int maxm = 105; namespace prime { ll phi[maxn][maxm]; int p[maxn], pi[maxn * 10], cnt; bool vis[maxn * 10]; inline void init() { for (int i = 2; i < maxn * 10; ++i) { if (!vis[i]) p[++cnt] = i; pi[i] = cnt; for (int j = 1; j <= cnt && i * p[j] < maxn * 10; ++j) { vis[i * p[j]] = 1; if (i % p[j] == 0) break; } } for (int n = 0; n < maxm; ++n) for (int m = 0; m < maxn; ++m) if (!n) phi[m][n] = m; else phi[m][n] = phi[m][n - 1] - phi[m / p[n]][n - 1]; } inline ll Phi(ll m, int n) { if (n == 0) return m; else if (p[n] >= m) return 1; else if (m < maxn && n < maxm) return phi[m][n]; return Phi(m, n - 1) - Phi(m / p[n], n - 1); } inline ll Pi(ll m) { if (m < maxn) return pi[m]; int s = sqrt(m), y = cbrt(m), n = pi[y]; ll res = Phi(m, n) + n - 1; for (int i = n + 1; p[i] <= s; ++i) res -= Pi(m / p[i]) - Pi(p[i]) + 1; return res; } } ll n, res; int p[maxn * 10], cnt, ntc; bool vis[maxn * 10]; inline void SK() { scanf ("%lld", &n); res = prime::Pi(n); ll maxn = sqrt(n); cnt = 0; for (int i = 2; i <= maxn; ++i) { if (!vis[i]) { p[++cnt] = i; ll temp = 1ll * i * i; while (temp <= n) { ++ntc; temp *= i; } } for (int j = 1; j <= cnt && i * p[j] <= maxn; ++j) { vis[i * p[j]] = 1; if (i % p[j] == 0) break; } } } int main() { prime::init(); SK(); printf ("%lld\n", res + ntc); }
参考资料:
wikipedia