题:https://www.luogu.com.cn/problem/P4213
学习粗:https://blog.csdn.net/weixin_43914593/article/details/104229700
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e6+7; //超过n^2/3,够大了 int prime[maxn]; //记录质数 bool vis[maxn]; //记录是否被筛; int mu[maxn]; //莫比乌斯函数值 ll phi[maxn]; //欧拉函数值 unordered_map<int,int> summu; //莫比乌斯函数前缀和 unordered_map<int,ll> sumphi; //欧拉函数前缀和 inline void init(){ //线性筛预计算一部分答案 int cnt = 0; vis[0] = vis[1] = 1; mu[1] = phi[1] = 1; for(int i=2;i<maxn;i++){ if(!vis[i]){ prime[cnt++] = i; mu[i] = -1; phi[i] = i-1; } for(int j=0;j<cnt && i*prime[j]<maxn;j++){ vis[i*prime[j]] = 1; if(i%prime[j]){ mu[i*prime[j]] = -mu[i]; phi[i*prime[j]] = phi[i]*phi[prime[j]]; } else{ mu[i*prime[j]] = 0; phi[i*prime[j]] = phi[i]*prime[j]; break; } } } for(int i=1;i<maxn;i++){ //最后,mu[]和phi[]改为记录1~maxn的前缀和。 mu[i] += mu[i-1]; phi[i] += phi[i-1]; } } inline int gsum(int x){ // g(i)的前缀和 return x; } inline int getsmu(int x){ if(x < maxn) return mu[x]; //预计算 if(summu[x]) return summu[x]; //记忆化 int ans = 1; //杜教筛公式中的 1 for(int l=2,r;l<=x;l=r+1){ //用整除分块计算杜教筛公式 r = x/(x/l); ans -= (gsum(r)-gsum(l-1))*getsmu(x/l); } return summu[x] = ans/gsum(1); } inline ll getsphi(int x){ if(x < maxn) return phi[x]; if(sumphi[x]) return sumphi[x]; //记忆化,每个sumphi[x]只用算一次 ll ans = 1LL*x*(x+1)/2; //杜教筛公式中的 n(n+1)/2 for(int l=2,r;l<=x;l=r+1){ //用整除分块计算杜教筛公式,这里算 sqrt(x)次 r = x/(x/l); ans -= (gsum(r)-gsum(l-1))*getsphi(x/l); } return sumphi[x] = ans/gsum(1); } int main(){ init(); //用线性筛预计算一部分 int t; scanf("%d",&t); while(t--){ int n; scanf("%d",&n); printf("%lld %d ",getsphi(n),getsmu(n)); } return 0; }