莫比乌斯反演
莫比乌斯函数
定义
- 莫比乌斯函数\(\mu(n)\)的定义域是\(N\)
- \(\mu(1)=1\)
- 当\(n\)存在平方因子时,\(\mu(n)=0\)
- 当\(n\)是素数或奇数个不同素数之积时,\(\mu(n)=-1\)
- 当\(n\)是偶数个不同素数之积时,\(\mu(n)=1\)
线性筛求\(\mu(n)\)
void prime(){
mu[1]=1;
for(int i=2;i<=n;++i){
if(!vis[i]) p[++m]=i,mu[i]=-1;//只有一个素因子,则mu[n]=-1
for(int j=1;j<=m && p[j]*i<=n;++j){
vis[p[j]*i]=1;
if(i%p[j]==0)break;//如果p[j]|i,说明一定存在平方因子,则跳过不更新mu[p[j]*i],这将意味着mu[p[j]*i]永远为0
mu[p[j]*i]=-mu[i];//如果mu[i]=0,则mu[p[j]*i]同样为0;如果mu[i]!=0,则mu[p[j]*i]加入一个新的素因子p[j],应当乘-1
}
}
}
性质
1) 对于任意正整数\(n\),\(\sum_{d|n}\mu(d)=[n=1]\)
2) 对于任意正整数\(n\),\(\sum_{d|n}\frac{\mu(d)}{d}=\frac{\phi(n)}{n}\)
证明
性质1.
-
当\(n=1\)时,原式\(=\mu(1)=1\),成立
-
当\(n\not =1\)时,将\(n\)分解成\(p_1^{a_1}p_2^{a_2}...p_k^{a_k}\)
只有所有质因子的个数为\(1\)时\(\mu(d)\)不为\(0\),所以假设由\(x\)个不同质因子的\(d\)有\(C_{k}^{x}\)个,则
性质2
莫比乌斯反演
定理
套路
套路1:优先枚举\(\gcd k\)
套路2:\(\gcd(i,j)=k等价于 \gcd(\frac ik,\frac jk)=1\)
套路3:莫比乌斯函数的性质\(\sum_{d|n}\mu(d)=[n=1]\)
套路4:优先枚举\(d\)
套路5:设\(dk=T\)
P2257
废话不说,直接开始推导。
这样后面可以在线性筛时做\(O(n)\)预处理+前缀和,前面可以做\(O(T\sqrt n)\)整除分块.
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e5+10,M=1e7+10;
typedef long long ll;
int p[M],mu[M],vis[M],sum[M],f[M];
int m,n,T;
void prime(){
mu[1]=1;
for(int i=2;i<M;++i){
if(!vis[i]) p[++m]=i,mu[i]=-1;
for(int j=1;j<=m && p[j]*i<M;++j){
vis[p[j]*i]=1;
if(i%p[j]==0){mu[p[j]*i]=0;break;}
mu[p[j]*i]=-mu[i];
}
}
for(int j=1;j<=m;++j){
for(int i=1;i*p[j]<M;++i){
f[i*p[j]]+=mu[i];
}
}
for(int i=1;i<M;++i)sum[i]=sum[i-1]+f[i];
}
void solve(){
ll ans=0;
for(int l=1,r=0;l<=n;l=r+1){
r=min(n/(n/l),m/(m/l));
ans+=(ll)(sum[r]-sum[l-1])*(ll)(n/l)*(ll)(m/l);
}
printf("%lld\n",ans);
}
int main(){
prime();
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);if(n>m)swap(n,m);
solve();
}
return 0;
}
-
注_1: 这里的变换时因为每一个\(d\)都由许多个\(i,j\)枚举而来,所以枚举\(d\)时直接乘上能转移过来的\(i,j\)的最大个数即可,而\(d\)最大能取到\(n\) (\(\gcd(n,m)=n\)时)
-
注_2: 这里的变换相当于\(1\)的逆操作,\(1\)时由大变小,而\(2\)时由小变大。一个\(T\)能转移到的所有\(d\)为\(d=\frac Tk (k|T,k\in prime)\),所以对这些\(d\)有贡献\(\mu(\frac Tk)\),要加在一起。
~~全long long计算真的慢,比int转long long 慢一倍~~
P3455
模板题.
这里给出一个与P2577不同的推式子的方法,大同小异
$$f(k)=\sum_{i=1}^a\sum_{j=1}^b[gcd(i,j)=k]\ F(n)=\sum_{n|k}f(k)=\lfloor\frac an\rfloor\lfloor \frac bn\rfloor\ f(n)=\sum_{n|k}\mu(\lfloor \frac kn\rfloor)F(k) $$ 之后退出来的式子与用\(\sum_{d|n}\mu(d)=[n=1]\)推出来的一样。
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=1e5+10,M=1e6+10;
int a,b,d,n,m,T;
int p[M],mu[M],vis[M],sum[M];
void prime(){
mu[1]=1;
for(int i=2;i<M;++i){
if(!vis[i]) p[++m]=i,mu[i]=-1;
for(int j=1;j<=m && i*p[j]<M;++j){
vis[i*p[j]]=1;
if(i%p[j]==0) break;
mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<M;++i)sum[i]=sum[i-1]+mu[i];
}
void solve(){
ll ans=0;
n=a/d,m=b/d;
if(n>m)swap(n,m);
for(int l=1,r=0;l<=n;l=r+1){
r=min(n/(n/l),m/(m/l));
ans+=(ll)(sum[r]-sum[l-1])*(ll)(n/l)*(ll)(m/l);
}
printf("%lld\n",ans);
}
int main(){
prime();
scanf("%d",&T);
for(int i=1;i<=T;++i){
scanf("%d%d%d",&a,&b,&d);
solve();
}
return 0;
}
P3327
此时后面两项可以\(O(n\sqrt n)\)整除分块预处理出\(f\)数组,前面也可以用整除分块\(O(T\sqrt n)\)做。
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=7e4+10;
int p[N],vis[N],mu[N],sum[N];
int T,n,m,cnt;
ll f[N];
void prime() {
mu[1]=1;
cnt=0;
for(int i=2; i<N; ++i) {
if(!vis[i])p[++cnt]=i,mu[i]=-1;
for(int j=1; j<=cnt && i*p[j]<N; ++j) {
vis[i*p[j]]=1;
if(i%p[j]==0)break;
mu[i*p[j]]=-mu[i];
}
}
for(int i=1; i<N; ++i) {
ll ans=0;
for(int l=1,r=0; l<=i; l=r+1) {
r=(i/(i/l));
ans+=(ll)(r-l+1)*(ll)(i/l);
}
f[i]=ans;
}
for(int i=1; i<N; ++i)sum[i]=sum[i-1]+mu[i];
}
void solve() {
ll ans=0;
for(int l=1,r=0; l<=n; l=r+1) {
r=min(n/(n/l),m/(m/l));
ans+=(ll)(sum[r]-sum[l-1])*(ll)(f[m/l])*(ll)(f[n/l]);
}
printf("%lld\n",ans);
}
int main() {
prime();
scanf("%d",&T);
for(int i=1; i<=T; ++i) {
scanf("%d%d",&n,&m);if(n>m) swap(n,m);
solve();
}
return 0;
}
P1829
此时可以两次分块来做,一次在\(\lfloor\frac n{dk}\rfloor\)时,一次在\(\lfloor\frac nd\rfloor\)时,时间复杂度为\(O(\sqrt n * \sqrt n)=O(n)\)
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int N=1e7+10,P=20101009,inv=10050505;
int n,m,cnt;
int vis[N],mu[N],p[N],sum[N];
void prime() {
mu[1]=1;cnt=0;
for(int i=2; i<N; ++i) {
if(!vis[i])p[++cnt]=i,mu[i]=-1;
for(int j=1; j<=cnt && i*p[j]<N; ++j) {
vis[i*p[j]]=1;
if(i%p[j]==0)break;
mu[i*p[j]]=-mu[i];
}
}
for(int i=1; i<N; ++i)sum[i]=(sum[i-1]+i*i%P*mu[i])%P;
}
int _solve(int _n,int _m) {
int tmp=0;
for(int _l=1,_r=0; _l<=_n; _l=_r+1) { //k
_r=min(_n/(_n/_l),_m/(_m/_l));
tmp=(tmp+ (sum[_r]-sum[_l-1])%P* (1+(_n/_l))%P*inv%P*(_n/_l)%P* (1+(_m/_l))%P*inv%P*(_m/_l)%P)%P;
}
return tmp;
}
int _solve1(int _n,int _m) {
int tmp=0;
for(int _l=1; _l<=_n; ++_l) { //k
tmp=(tmp+mu[_l]*_l%P*_l%P*(1+_n/_l)%P*(_n/_l)%P*inv%P*(1+_m/_l)%P*(_m/_l)%P*inv%P)%P;
}
return tmp;
}
void solve1(){
int ans=0;
for(int l=1; l<=n; ++l) { //d
int _n=n/l,_m=m/l;
int tmp=_solve(_n,_m);
ans=(ans+ l*tmp%P)%P;
}
ans=(ans%P+P)%P;
printf("%lld",ans);
}
void solve() {
int ans=0;
for(int l=1,r=0; l<=n; l=r+1) { //d
r=min(n/(n/l),m/(m/l));
int _n=n/l,_m=m/l;
int tmp=_solve(_n,_m);
ans=(ans+ ((l+r)*inv%P*(r-l+1))%P*tmp)%P;
}
ans=(ans%P+P)%P;
printf("%lld",ans);
}
signed main() {
prime();
scanf("%lld%lld",&n,&m);
if(n>m)swap(n,m);
solve();
return 0;
}
~~十年OI一场空,不开long long 见祖宗;取模操作也不要吝啬时间复杂度,尽可能地多膜一膜,防止溢出从你我做起~~
-
注_1: 这里从除以\(d\)变成乘\(d\),即在原始基础上乘了\(d^2\),是因为\(i,j\)地范围都缩小了\(d\),而不能忽略后面地\(i* j\)
-
注_2: 乘\(k^2\)地原因同 注_1
P3911
- 注_1: 因为\(i,j\)的大小减小,而\(c\)的下标不能变,所以一定要乘\(d\)