把 qiandao(x) 转化为 x-phi(x),再用分段筛批量计算短区间内每个数的欧拉函数。
OJ: luogu
题目 ID: P3601
难度:提高+/省选-
标签:欧拉函数分段筛数论筛法
日期: 2026-07-16 19:20
题意
定义
右端点可达
思路
按定义枚举只能验证小数据
最直接的方法是对每个
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-19 11:59
* update_at: 2026-07-19 11:59
*/
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据朴素解,按定义枚举 1..x 并检查最大公约数。
const int MOD = 666623333;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long left, right;
cin >> left >> right;
long long answer = 0;
for (long long value = left; value <= right; value++) {
for (long long other = 1; other <= value; other++) {
if (gcd(value, other) != 1) answer++;
}
answer %= MOD;
}
cout << answer << '\n';
return 0;
}这个程序适合验证很小的区间,但当
转化为欧拉函数
欧拉函数
问题转化为批量计算区间内每个数的欧拉函数。若
则:
所以只要找出
在短区间上做分段质因数分解
不能从
remaining[i]:尚未除去已知质因子的部分,初始为; phi_value[i]:欧拉函数当前值,初始也为。
先用线性筛求出所有不超过
- 对这个位置的
phi_value乘上; - 从
remaining中把所有因子除尽。
同一个质因子无论指数是多少,都只应在欧拉函数乘积中出现一次,所以先更新一次 phi_value,再用 while 除尽 remaining。
为什么最后的剩余部分至多是一个质数
所有 remaining[i] 仍大于 1,它一定是质数。
反设它是合数,那么它至少有一个质因子不超过自身平方根,也不超过原数平方根,进而不超过
因此最后只需对大于 1 的剩余因子再更新一次欧拉函数,就完成了完整分解。
原 main.py 使用相同数学模型,但百万长度区间上的多轮 Python 循环、任意精度整数运算和数组访问常数较大,未通过评测。C++ 用两个连续的 long long 数组完成原地分解,并用整数下标访问区间。
正确性说明
对区间内任意
小质数处理结束后,剩余部分只能是 1 或一个大质数;若是大质数,程序再执行一次相同更新。因此 phi_value[x-l] 最终等于欧拉乘积公式给出的
由
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-19 11:59
* update_at: 2026-07-19 11:59
*/
#include <bits/stdc++.h>
using namespace std;
const int MOD = 666623333;
const int MAX_LENGTH = 1000005;
const int MAX_PRIME = 1000005;
bool composite[MAX_PRIME];
int primes[MAX_PRIME];
int prime_count;
long long remaining[MAX_LENGTH];
long long phi_value[MAX_LENGTH];
void build_primes(int limit) {
for (int i = 2; i <= limit; i++) {
if (!composite[i]) primes[prime_count++] = i;
for (int j = 0; j < prime_count; j++) {
long long multiple = 1LL * i * primes[j];
if (multiple > limit) break;
composite[multiple] = true;
if (i % primes[j] == 0) break;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long left, right;
cin >> left >> right;
int length = (int)(right - left + 1);
for (int i = 0; i < length; i++) {
remaining[i] = left + i;
phi_value[i] = left + i;
}
long long square_root = sqrt((long double)right);
while ((square_root + 1) * (square_root + 1) <= right) square_root++;
while (square_root * square_root > right) square_root--;
build_primes((int)square_root);
for (int i = 0; i < prime_count; i++) {
long long prime = primes[i];
long long first = (left + prime - 1) / prime * prime;
for (long long multiple = first; multiple <= right; multiple += prime) {
int index = (int)(multiple - left);
phi_value[index] = phi_value[index] / prime * (prime - 1);
while (remaining[index] % prime == 0) {
remaining[index] /= prime;
}
}
}
long long answer = 0;
for (int i = 0; i < length; i++) {
if (remaining[i] > 1) {
long long prime = remaining[i];
phi_value[i] = phi_value[i] / prime * (prime - 1);
}
long long value = left + i;
answer += (value - phi_value[i]) % MOD;
if (answer >= MOD) answer -= MOD;
}
cout << answer << '\n';
return 0;
}复杂度
设区间长度为
- 线性筛到
的时间复杂度为 。 - 分段枚举的总次数约为
,可写为 ;除去质因子幂次的总次数也受每个数的质因子个数限制。 - 总时间复杂度为
。 - 质数筛和区间数组占用
空间。 brute.cpp按定义枚举,只适用于很小的。
总结
大端点并不允许筛到