一、前导知识
二、朴素思路
我们先不管数据范围是不是\(10^{12}\),先思考就朴素的作法是什么样的:
\(qiandao(x)=x−ϕ(x)\) ,假设\(x\)在我们可控的数据范围内,就是筛出数据范围内的\(ϕ(x)\), 然后利用公式计算一下\(sum\)和,再\(mod\)一下就行。
这样做的知识点就是欧拉函数的筛法,直接套模板就行。
数据样例的说明:
1、数据范围 60% l,\(r<=10^7\)
2、数据范围 100% l,\(r<=10^{12}\)
所以,不出意外,上面的朴素作法,只能过掉\(10^7\)的数据,就是PASS掉\(6\)个测试点,得了\(60\)分,无法\(AC\)掉本题。
但不管怎样,这是这道题最本质的作法,是最应该第一步想到的,是没有更好办法的情况下的最优解,这是前进路上的必经之路。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
//左右边界
LL l, r;
int ans;//答案
const int N = 1e8 + 10;
const int MOD = 666623333;
int primes[N]; //保存的是每一个质数
int cnt; //cnt代表质数下标,就是到第几个了
int phi[N]; //欧拉函数值
bool st[N]; //是不是已经被筛掉了
// 求1-N之间每一个数的欧拉函数
// 线性筛法求质数的过程当中,捎带着求出每个数的欧拉函数值,其实还可以顺便求出很多东西。
void get_eulers(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
phi[t] = phi[i] * primes[j];
break;
} else {
phi[t] = phi[i] * (primes[j] - 1);
}
}
}
}
int main() {
//1、读入
scanf("%lld%lld", &l, &r);
//2、大力出奇迹!线性筛欧拉函数
get_eulers(r);
//3、qiandao(x)=x−ϕ(x),计算sum,再取一下模
for (LL i = l; i <= r; i++) ans = (ans + i - phi[i]) % MOD;
//4、输出答案
printf("%d\n", ans);
return 0;
}
三、终极解法
上面的代码为什么会只得\(60\)分呢?究其原因,是因为\(1<=l<=r<=10^{12}\),无法定义这么大的数组,然后再通过欧拉函数筛法获取欧拉函数值,空间上不允许啊。看来筛法求欧拉函数值也是有限制的。
换个思路吧,我们还是想办法对\(l \sim r\)进行遍历,因为\(r-l<=10^6\),所以遍历是没有问题的。对这个区间内的数字,求每一个数字的所有质因数。为啥要分解质因数呢?
因为欧拉函数的通项公式告诉我们,只要我们能拿到一个数的所有质数因数,就可以获取它的欧拉函数值!
补习一下欧拉函数的知识!
欧拉函数的通项公式
$\large φ(n)=n*(1-\frac{1}{p_1})(1-\frac{1}{p_2})(1-\frac{1}{p_3})*(1-\frac{1}{p_4})……(1-\frac{1}{p_n})$
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const int MOD = 666623333;
//欧拉筛[线性筛法]
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
LL l, r, ans;
//一维是[l,r]的映射位移,二维是一个动态数组,记录当前这个数字有哪些质数因子
vector<int> vec[N];
int main() {
//输入
cin >> l >> r;
//线性筛,筛出质数小因子范围
get_primes(sqrt(r));
//遍历每个小质数因子
for (int i = 0; i < cnt; i++) {
int p = primes[i];
//1、利用数组下标的位移,巧妙记录数据
//2、找到大于l的第一个p的倍数,然后,每次增加p,相当于找出p的整数倍
for (LL j = ((l - 1) / p + 1) * p; j <= r; j += p)
vec[j - l].push_back(p);
}
//如果还存在大的质数因子
for (LL i = l; i <= r; i++) {
LL tmp = i; //将i拷贝出来给了tmp,tmp要不断的减少啦,而i要保留。
LL phi = i; //欧拉函数值初始化为i
//当数字是i时,找到对应的质因子列表中的每一个质数
for (int p: vec[i - l]) {
//这里需要仔细理解欧拉函数的基本求法
phi = phi / p * (p - 1);
//如果还存在质数因子p,就除干净为止,因为欧拉函数是与因子的幂次无关,只与因子有关
while ((tmp % p) == 0) tmp /= p; //除干净为止
}
//如果还存在大的质数因子
if (tmp > 1)phi = phi / tmp * (tmp - 1);
//计算结果
ans = (ans + i - phi) % MOD;
}
//输出答案
cout << ans << endl;
return 0;
}