luogu3601 签到题
题目大意
求
\[\sum_{i=l}^{r}(i-\varphi(i))
\]
\(l,r\leq 10^{12}, r-l<10^6\)
思路
看到这个数据范围,不能直接用线性筛。我们要想求\(\varphi\),需要用时间复杂度为根号的算法(往往是反演)。要想反演,我们要由最朴素的方法中找到思路。
以下是求一个\(\varphi(x)\)值的普速算法的伪代码。
for the x:
foreach prime p (in [1, sqrt(x)] && p|x)
deal(&x, p)
if(x>1)
deal_as_big_prime(x)
我们发现枚举的\(p\)都在\(\sqrt{x}\)的范围内。因此我们可以利用每一个\([1,\sqrt{r}]\)内的质数求出\([l,r]\)之间的所有\(\varphi\)值。伪代码:
foreach prime p (in [1, sqrt(r)])
foreach x[i] (i in [l, r] && p|x[i])//i是x[i]的初始值,x[i]以后会改变
deal(&x[i], p)
foreach x[i] (x[i]>1)
deal_as_big_prime(x[i])
完整代码:
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
#define ll long long
#define Phi(i) Phis[i - l]
#define X(i) Xs[i - l]
const int MAX_N = 1000010, P = 666623333;
ll Phis[MAX_N], Xs[MAX_N];
int Primes[MAX_N];
int GetPrime(int *prime, int n)
{
static bool NotPrime[MAX_N];
int primeCnt = 0;
for (int i = 2; i <= n; i++)
{
if (!NotPrime[i])
prime[primeCnt++] = i;
for (int j = 0; j < primeCnt; j++)
{
if (i*prime[j] > n)
break;
NotPrime[i*prime[j]] = true;
if (i%prime[j] == 0)
break;
}
}
return primeCnt;
}
void GetPhi(ll l, ll r, int primeCnt)
{
for (ll i = l; i <= r; i++)
Phi(i) = X(i) = i;
for (int i = 0; i < primeCnt; i++)
{
ll lb = Primes[i] * (l / Primes[i]), rb = Primes[i] * (r / Primes[i]);
if (lb < l)
lb += Primes[i];
for (ll j = lb; j <= rb; j+=Primes[i])
{
Phi(j) = Phi(j) / Primes[i] * (Primes[i] - 1);
while (X(j) % Primes[i] == 0)
X(j) /= Primes[i];
}
}
for (ll j = l; j <= r; j++)
if (X(j) > 1)
Phi(j) = Phi(j) / X(j) * (X(j) - 1);
}
ll StatPhi(ll l, ll r)
{
ll ans = 0;
for (ll i = l; i <= r; i++)
ans = (ans + i - Phi(i)) % P;
return ans;
}
int main()
{
ll l, r;
scanf("%lld%lld", &l, &r);
int primeCnt = GetPrime(Primes, sqrt(r));
GetPhi(l, r, primeCnt);
printf("%lld\n", StatPhi(l, r));
return 0;
}