题意
求T组
dp法
引我之前文章的分析:
易知以为
的倍数的有
个,则
为
倍数的有
个,记为
又记表示
的个数,
因为,故可以倒着更新
最后去重就是答案
该方法时间复杂度为,虽然该方法对于后两题会
,不过其展现了大概的思路,为后面两种方法(特别是莫比乌斯反演提供了可行的方向)
#pragma GCC optimize(3)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
using LL = long long;
const int MAXN = 2e6 + 5;
LL N, ans;
LL f[MAXN], g[MAXN];
int main(){
while(cin >> N){
if(!N) break;
ans = 0;
int i, j;
for(i = 1; i <= N; i++) f[i] = (N / i) * (N / i);
for(i = N; i >= 1; i--){
LL tmp = 0;
for(j = 2 * i; j <= N; j += i) tmp += g[j];
g[i] = f[i] - tmp;
}
for(i = 1; i <= N; i++) ans += i * g[i];
ans = (ans - N * (N + 1) / 2) / 2;
cout << ans << endl;
}
return 0;
}
欧拉函数
,若考虑
那么针对给定
,
对其贡献为
设,那么
,那么对于询问
通过即可以针对询问进行查询(针对第二题的大批量询问)
莫比乌斯反演
回到的分析,我们针对
为
倍数的
与
的
进行反演
(这里要整数分块求和,具体解释可以看看整体约数求和)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
using LL = long long;
const int MAXN = 5e6 + 5;
LL N;
int mu[MAXN], phi[MAXN], pre[MAXN], prime[MAXN], cnt_prime;
bool noprime[MAXN];
void sieve(int top);
LL g[MAXN], G[MAXN];
LL solve(int);
int main(){
sieve(3e5);
int i, j;
for(i = 1; i <= 3e5; i++)
for(j = i + i; j <= 3e5; j += i)
g[j] += i * phi[j / i];
for(i = 1; i <= 3e5; i++) G[i] = G[i - 1] + g[i];
while(cin >> N && N){
//Euler_fuc
cout << G[N] << endl;
//Mobius inversion
int i;
LL ans = 0;
for(i = 1; i <= N; i++)
ans += i * solve(N / i);
//optimize
int l, r;
LL ans = 0;
for(l = 1; l <= N; l = r + 1){
r = N / (N / l);
ans += solve(N / l) * (r + l) * (r - l + 1) / 2;
}
ans = (ans - N * (N + 1) / 2) / 2;
cout << ans << endl;
}
return 0;
}
LL solve(int n){
LL res = 0, l, r;
for(l = 1; l <= n; l = r + 1)
r = n / (n / l), res += (pre[r] - pre[l - 1]) * (n / l) * (n / l);
return res;
}
void sieve(int top){
mu[1] = 1, phi[1] = 1;
int i, j;
for(i = 2; i <= top; i++){
if(!noprime[i]) prime[++cnt_prime] = i, mu[i] = -1, phi[i] = i - 1;
for(j = 1; j <= cnt_prime && prime[j] * i <= top; j++){
noprime[prime[j] * i] = true;
if(i % prime[j] == 0){
mu[prime[j] * i] = 0;
phi[prime[j] * i] = phi[i] * prime[j];
break;
}else{
mu[i * prime[j]] = -mu[i];
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
for(i = 1; i <= top; i++) pre[i] = pre[i - 1] + mu[i];
}
特别的单次询问这种方法可以做到
但如果多次询问则需要进一步化解
通过处理到最内层的前缀和则可以做到每次询问
for(i = 1; i <= top; i++)
for(j = i; j <= top; j += i)
sum[j] += mu[j / i] * i;
for(i = 1; i <= top; i++) sum[i] += sum[i - 1];
int l, r; LL ans = 0;
for(l = 1; l <= top; l = r + 1){
r = min(N / (N / l), M / (M / l));
ans += (LL)(N / l) * (M / l) * (sum[r] - sum[l - 1]);
}
PS:这里还有一种计算方式,利用欧拉函数
(这里和上述欧拉函数中
意义不同(两种角度))