Project Euler 625
May 3, 2023
I was reading a lot of stuff about Dirichlet Convolution and how to solve sums over arithmetic functions. There are a lot of resources and I don't consider that it's an easy topic. Even when I don't use Dirichlet here, It's something that I'll try to practice more because it's really useful when you're working with Mobius function. Maybe I need to review my past codes to see if I can improve them.
Statement
Find . Give your answer modulo .
Solution
Naive solution
Let's try with the simplest and naive solution, just doing a nested for:
long long G = 0;
for(long long j=1; i<=N; i++){
for(long long i=1; i<=j; i++) G += __gcd(i, j);
}
Of course, the time complexity of this solution is: and not feasible to compute since the value for is .
Linear solution
From my previous solution in a past blog, using the Mobius function it is possible to reduce the solution to and by applying the Mertens function optimization, it is possible to reduce it to .
Even when this time complexity is acceptable (it could take a few hours), the memory utilization is close to the order of terabytes, so it is not efficient.
long long G = 0;
vector <int> values;
vector <pair <int,int> > range;
fill(n, values, range);
for(int i=0; i<values.size(); i++){
int v = values[i];
long long aux = 0;
vector <int> n_v;
vector <pair <int,int> > n_r;
fill(v, n_v,n_r);
for(int j=0; j<n_v.size(); j++){
int k = n_v[j];
long long s = 1LL*k*k-(1LL*k*(k+1))/2;
aux += s*(s_mobius[n_r[j].first] - s_mobius[n_r[j].second-1]);
}
long long s = (1LL*range[i].first*(range[i].first+1))/2;
s -= (1LL*range[i].second*(range[i].second-1))/2;
G += aux*s;
}
Sub-linear solution
Since even the linear solution is not acceptable for this problem. We need to find a sub-linear approach, in this case, I will try to formulate a solution, which can be computed in few seconds.
First, I'll calculate the sum of :
The last line is the dirichlet notation, it could be useful.
Now, we can use to calculate :
As you can see, we want to calculate sums of , so let's to reduce it:
This is a recursive definition. It's not hard to see that the time complexity for this is .
Similar to the explained in a previous post, the list of possible values of has a size of . I will use the hyperbola method to calculate it:
To optimize the complexity, suppose that we will do a sieve until , so the overall complexity will be:
Code
Here is the code, it's not so fast due to mod
operations.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define N 10000000
#define MOD 998244353
int phi[N];
int c_Phi[N];
unordered_map <ll, int> m_Phi;
void cphi(){
phi[1] = 1;
for(int i=2; i<N; i++){
if(!phi[i]){
phi[i] = i-1;
for(ll j=2*i; j<N; j+=i){
if(phi[j] == 0) phi[j] = j;
phi[j] = phi[j]/i*(i-1);
}
}
}
c_Phi[1] = phi[1];
for(int i=2; i<N; i++) c_Phi[i] = (c_Phi[i-1] + phi[i])%MOD;
}
int T(ll n){
n %= MOD;
ll ans = n*(n+1);
ans %= MOD;
ans *= 499122177;
return ans%MOD;
}
ll Phi(ll n){
if(n < N) return c_Phi[n];
if(m_Phi.find(n) != m_Phi.end()) return m_Phi[n];
ll ans = T(n);
ll sqrt_n = floor(sqrt(n));
for(int m=1; m < n/sqrt_n; m++){
ans -= 1LL*(n/m - n/(m+1))*c_Phi[m];
ans %= MOD;
}
for(int m=2; m<=sqrt_n; m++){
ans -= Phi(n/m);
ans %= MOD;
}
ans += MOD; ans %= MOD;
m_Phi[n] = ans;
return ans;
}
ll G(ll n){
ll ans = 0;
int sqrt_n = floor(sqrt(n));
ans -= 1LL*T(sqrt_n)*Phi(sqrt_n);
ans %= MOD;
for(int i=1; i<=sqrt_n; i++){
ans += Phi(n/i)*i;
ans %= MOD;
}
for(int i=1; i<=sqrt_n; i++){
ans += 1LL*phi[i]*T(n/i);
ans %= MOD;
}
ans += MOD; ans %= MOD;
return ans;
}
int main(){
cphi();
ll n; cin >> n;
cout << G(n) << '\n';
return 0;
}
Resource:
I want to list them because I would like to go deeper into them and should review them further at my leisure.