一. 思路
1≤l≤r≤1012,r−l≤106。很显然,传统的用欧拉函数是积性函数这条性质的从 1 扫到区间右端的方法肯定不行。
虽然传统的方法不行。但欧拉函数还有一条有用的公式:φ(n)=n×i=1∏npipi−1 。也就是说,我们只要知道 l∼r 中所有数的质因数分解就好了。不难发现一个数 n 的质因数中有一个或零个大于 n 。那么我们把小于等于 r 的质数姑且称为“小质数”;大于 r 的叫“大质数”。
先预处理出所有小质数(也就是 1∼106 内的)开一个数组 inv 每个位置预处理为下标的值(就是 φ(n)=n×i=1∏npipi−1 中的等式右边的 n),然后用倍数法枚举所有小质数在 l∼r 区间中的倍数,将相应的 invi 乘上 i=1∏npipi−1。
当然,不要忘了大质数,因为每个数至多有一个大质数,所以大质数也很好处理。开一个数组 bidprime 全初始化为下标。用倍数法枚举 primei 的倍数枚举到相应的 invj 时,顺便把 bigprimej 中所有的的因数 primei 剔除。最后就得到了每个数的大质数。
二. 细节
最重要的细节就是枚举小指数的倍数时从几枚举了。设该小质数为 p,区间左端点为 l 。答案是 max{p2,⌈pl⌉×p}。
为什么呢?首先小于 p2 的 p 的倍数在枚举 2,3 等比它更小的质数时就已经枚举过了。而 $\left\lceil\dfrac{l}{p}\right\rceil \times p $ 是大于等于 l 的第一个 p 的倍数。
三. 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include<cstdio> #include<cmath> #define re register #define int long long const int MAXprime = 1e6; const int MAXn = 1e6; const int MOD = 666623333;
template <class T> inline T max(T a, T b) { return a > b ? a : b; }
int cntp, prime[MAXprime / 5 + 100]; bool notp[MAXprime + 10]; void PrimeSieve(int up) { notp[1] = 1; for (re int i = 2; i <= up; ++i) { if (!notp[i]) prime[++cntp] = i; int upj = up / i; for (re int j = 1; j <= cntp && prime[j] <= upj; ++j) { notp[i * prime[j]] = 1; if (!(i % prime[j])) break; } } }
int ans, l, r, phi[MAXn + 10], bigprime[MAXn + 10]; signed main() { PrimeSieve(MAXprime); scanf("%lld %lld", &l, &r); for (re int i = l; i <= r; ++i) { phi[i - l] = bigprime[i - l] = i; } for (re int i = 1; i <= cntp; ++i) { for (re int j = max(prime[i] * prime[i], (int)ceil((double)l / prime[i]) * prime[i]); j <= r; j += prime[i]) { phi[j - l] = phi[j - l] / prime[i] * (prime[i] - 1); while (!(bigprime[j - l] % prime[i])) { bigprime[j - l] /= prime[i]; } } } for (re int i = l; i <= r; ++i) { if (bigprime[i - l] > 1) { phi[i - l] = phi[i - l] / bigprime[i - l] * (bigprime[i - l] - 1); } } for (re int i = l; i <= r; ++i) { ans = (ans + i - phi[i - l]) % MOD; } printf("%lld\n", ans); }
|