牛客练习赛72D——brz的函数(莫比乌斯反演)
令所求的式子为
\(f(n)=\Sigma_i\Sigma_j\mu(ij)\)
则可以写出如下递推式,画个 \(n*n\) 的表格可辅助理解
\(f(n)=f(n-1)+2\Sigma_{x=1}^n\mu(xn)\)
考虑 \(\Sigma_{x=1}^n\mu(xn)\) 的含义,这里先直接给出结果:
\(\Sigma_{x=1}^n\mu(xn)=\Sigma_{x=1}^n[gcd(x,n)=1]*\mu(x)*\mu(n)\)
上式的含义最简洁的一种理解方式是,由于莫比乌斯函数是积性的,因此当二者gcd为1时,有 \(\mu(xn)=\mu(x)\mu(n)\);而不为1的时候 \(xn\) 一定有平方因子,因此乘起来整个都是0。因此,可以写成上式。
使用经典结论 \([dog=1]=\Sigma_{d|dog}\mu(d)\) 可以继续得到:
\(\Sigma_{x=1}^n\mu(xn)=\Sigma_{x=1}^n[gcd(x,n)=1]*\mu(x)*\mu(n)\)
\(=\mu(n)\Sigma_{x=1}^n\mu(x)\Sigma_{d|gcd(x,n)}\mu(d)\)
交换求和次序
\(=\Sigma_{d|n}\mu(d)*\Sigma_{d|x,x\leq n}\mu(x)\)
此时似乎很难往下化简了,我也一度陷入了懵逼当中,但当我冷静下来时,我发现化简到这一步已经够用了,因为上式直接计算似乎是(好吧我瞎猜的,应该差不多)个 \(O(\sqrt{n}*log(n))\) 的复杂度,所以就直接计算就好了。
这样,总的程序复杂度就是 \(O(n\sqrt{n}*log(n))\) ,可以写了。
具体实现的时候,要预处理出每个数的所有因数,并且要在发现某 \(\mu\) 值为零时立刻continue剪个枝才能通过此题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,a,n) for(int i=n;i>=a;i--)
#define pb push_back
#define SZ(x) ((int)(x).size())
#define fastin ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef pair<int,int> pii;
typedef double db;
const int N=2e5+5;
int tot,flg[N+5],p[N+5],mu[N+5];
void getMu() {
mu[1] = 1;
for (int i = 2; i <= N; ++i) {
if (!flg[i]) p[++tot] = i, mu[i] = -1;
for (int j = 1; j <= tot && i * p[j] <= N; ++j) {
flg[i * p[j]] = 1;
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}
int ans[50010],t,n;
vector<int> fac[50010];
int main(){
getMu();
ans[1]=1;
rep(i,1,50000){
for(int j=i;j<=50000;j+=i){
fac[j].pb(i);
}
}
rep(i,2,50000){
if(!mu[i]){
ans[i]=ans[i-1];continue;
}
int cof=2*mu[i];
int sum=0;
for(auto d:fac[i]){
if(!mu[d]) continue;
int tmp=0;
for(int x=d;x<i;x+=d){
tmp+=mu[x];
}
tmp*=mu[d];
sum+=tmp;
}
ans[i]=ans[i-1]+cof*sum;
}
cin>>t;
while(t--){
cin>>n;
printf("%d\n",ans[n]);
}
return 0;
}