莫比乌斯反演练习

线性筛板子

const int N = 1e6+10;
int phi[N], mu[N], p[N], cnt, vis[N];
void init() {
	phi[1] = mu[1] = 1;
	for (int i=2; i<N; ++i) {
		if (!vis[i]) p[++cnt]=i,mu[i]=-1,phi[i]=i-1;
		for (int j=1,t; j<=cnt&&i*p[j]<N; ++j) {
			vis[t=i*p[j]] = 1;
			if (i%p[j]==0) {mu[t]=0,phi[t]=phi[i]*p[j];break;}
			mu[t]=-mu[i],phi[t]=phi[i]*phi[p[j]];
		}
	}
}

$O(nloglogn)$求$g=\mu * f$ 板子

void calc(int n, int *f, int *g) {
	REP(i,1,n) g[i] = f[i];
	REP(i,1,cnt) PER(j,1,n/p[i]) g[j*p[i]] -= g[j];
}

整除分块板子, 优化了一下除法次数

int mx = sqrt(n);
REP(i,1,mx) ans += (ll)(s[i]-s[i-1])*f(n/i);
for (int i=mx+1,j,k=n/i; i<=n; i=j+1,--k) {
    j = n/k;
    ans += (ll)(s[j]-s[i-1])*f(k);
}

 

 

练习1. luogu P2257 YY的GCD

大意: 求$\sum\limits_{i=1}^n \sum\limits_{j=1}^m[gcd(i,j)为素数]$

这种反演题目一般都是先转化为某个$g$函数的卷积, 再交换求和次序.

设$f(x)=[x为素数]$,  $g=\mu *f=\sum\limits_{p|n}\mu(\frac{n}{p})$, 则有

$\begin{align*} \sum\limits_{i=1}^n \sum\limits_{j=1}^mf(gcd(i,j)) &= \sum\limits_{i=1}^n \sum\limits_{j=1}^m \sum\limits_{d|gcd(i,j)} g(d) \\ &= \sum\limits_{d=1}^{min(n,m)}g(d)\sum\limits_{i=1}^n\sum\limits_{j=1}^m [d|gcd(i,j)] \\ &= \sum\limits_{d=1}^{min(n,m)}g(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor \end{align*}$

所以求出$g$以后整除分块即可, 考虑如何求$g$, $O(nloglogn)$当然很容易求出

这里思考一下$O(n)$的求法

对于$g(x)$, 假设$y$为$x$的最小素因子, 即$x=iy$

若$x$为素数, 显然有$g(x)=\mu(1)=1$

若$i%y==0$, $g(x)=\mu(i)$

若$i%y!=0$, $g(x)=-g(i)+mu(i)$

欧拉筛一次即可$O(n)-O(q\sqrt{n})$

const int N = 1e7+10;
int mu[N], p[N], cnt, vis[N], g[N];
void init() {
	mu[1] = 1;
	for (int i=2; i<N; ++i) {
		if (!vis[i]) p[++cnt]=i,mu[i]=-1,g[i]=1;
		for (int j=1,t; j<=cnt&&i*p[j]<N; ++j) {
			vis[t=i*p[j]] = 1;
			if (i%p[j]==0) {mu[t]=0,g[t]=mu[i];break;}
			mu[t]=-mu[i],g[t]=-g[i]+mu[i];
		}
		g[i]+=g[i-1];
	}
}

int main() {
	init();
	int t;
	scanf("%d", &t);
	REP(i,1,t) {
		int n, m;
		scanf("%d%d", &n, &m);
		ll ans = 0;
		if (n>m) swap(n,m);
		int lim = min(int(sqrt(n)*2),n);
		REP(i,1,lim) ans+=(ll)(g[i]-g[i-1])*(n/i)*(m/i);
		for (int i=lim+1,j; i<=n; i=j+1) {
			j = min(n/(n/i),m/(m/i));
			ans+=(ll)(g[j]-g[i-1])*(n/i)*(m/i);
		}
		printf("%lld\n", ans);
	}
}

练习2. Luogu P4213 杜教筛

大意: 求$\sum\limits_{i=1}^{n}\varphi(i),\sum\limits_{i=1}^{n}\mu(i)$
杜教筛是用来求一类积性函数前缀和的算法, 核心思想是利用公式
$$g(1)S(n)=\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{i=2}^n g(i)S(\lfloor \frac{n}{i}\rfloor)$$
其中$S(n)$是我们要求的$f(n)$的前缀和, $g(n)$是我们需要构造的一个函数, 需要满足$f*g$和$g$的前缀和可以$O(1)$求出.
可以证明上面这个递归式调用的不同的$S(n)$不超过$2\sqrt{n}$个, 即
$$S(1),S(2),...,S(\sqrt{n}),S(\frac{n}{1}),S(\frac{n}{2}),...,S(\frac{n}{\sqrt{n}})$$
对于$i\le n^{\frac{2}{3}}$的$S(i)$, 先暴力求出.
而对于$i>n^{\frac{2}{3}}$的$S(i)$, 即$S(\frac{n}{1}),S(\frac{n}{2}),...,S(\frac{n}{n^{\frac{1}{3}}})$, 只有$n^{\frac{1}{3}}$项, 可以每一项$\sqrt{n}$求出
所以总复杂度为$O(n^{\frac{2}{3}})$

对于本题来说, 对于$\sum\limits_{i=1}^n\mu(i)$取$g=\boldsymbol 1$, 就有$f*g=\varepsilon$, 对于$\sum\limits_{i=1}^{n}\varphi(i)$同理

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <math.h>
#include <set>
#include <map>
#include <queue>
#include <string>
#include <string.h>
#include <bitset>
#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 hr putchar(10)
#define pb push_back
#define lc (o<<1)
#define rc (lc|1)
#define mid ((l+r)>>1)
#define ls lc,l,mid
#define rs rc,mid+1,r
#define x first
#define y second
#define io std::ios::sync_with_stdio(false)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int P = 1e9+7, INF = 0x3f3f3f3f;
ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}
ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;}
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
//head


const int N1 = ~0u>>1, N2 = 2e6, N3 = 1300;

int N;
ll phi[N2], phi2[N3];
int mu[N2], mu2[N3];
int vis[N3], v[N2];
int p[N2], cnt;

inline void init(int n) {
    phi[1] = mu[1] = 1;
    REP(i,2,n) {
        if (!v[i]) p[++cnt]=i,mu[i]=-1,phi[i]=i-1;
        for (int j=1,t; j<=cnt&&i*p[j]<=n; ++j) {
            v[t=i*p[j]] = 1;
            if (i%p[j]==0) {mu[t]=0,phi[t]=phi[i]*p[j];break;}
            mu[t]=-mu[i],phi[t]=phi[i]*phi[p[j]];
        }
    }
    REP(i,2,N2-1) mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}

inline ll sum_phi(int n) {
    if (n<N2) return phi[n];
    int x = N/n;
    if (vis[x]) return phi2[x];
    vis[x] = 1;
    ll &ans = phi2[x];
    ans = (ll)n*(n+1)/2;
    int mx = sqrt(n);
    REP(i,2,mx) ans -= sum_phi(n/i);
    for (int i=mx+1,j,k=n/i; i<=n; i=j+1,--k) {
        j = n/k;
        ans -= (j-i+1)*sum_phi(k);
    }
    return ans;
}
inline int sum_mu(int n) {
    if (n<N2) return mu[n];
    int x = N/n;
    if (vis[x]) return mu2[x];
    vis[x] = 1;
    int &ans = mu2[x];
    ans = 1;
    int mx = sqrt(n);
    REP(i,2,mx) ans -= sum_mu(n/i);
    for (int i=mx+1,j,k=n/i; i<=n; i=j+1,--k) {
        j = n/k;
        ans -= (j-i+1)*sum_mu(k);
    }
    return ans;
}

int main() {
    init(N2-1);
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &N);
        if (!N) {puts("0\n0");continue;}
        memset(vis,0,sizeof vis);
        ll ans1 = sum_phi(N);
        memset(vis,0,sizeof vis);
        int ans2 = sum_mu(N);
        printf("%lld %d\n", ans1, ans2);
    }
}

练习3. Luogu P3768 简单数学题

大意: 求$\sum\limits_{i=1}^n\sum\limits_{j=1}^nijgcd(i,j)$

又是个杜教板子题, 简单推一下.

$\begin{align*} \sum\limits_{i=1}^n\sum\limits_{j=1}^nijgcd(i,j) &= \sum\limits_{i=1}^n\sum\limits_{j=1}^nij\sum\limits_{d|gcd(i,j)} \varphi(d) \\&= \sum\limits_{d=1}^n\varphi(d)\sum\limits_{i=1}^n\sum\limits_{j=1}^nij[d|gcd(i,j)] \\ &= \sum\limits_{d=1}^nd^2\varphi(d)\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}i\sum\limits_{j=1}^{\lfloor \frac{n}{d}\rfloor}j\end{align*}$

对这个式子用整除分块, 转化为求$\sum\limits_{i=1}^ni^2\varphi(i)$

按照杜教的套路, 这里可以凑一个函数$g=Id^2$, 这样$f*g=Id^3$, 总的复杂度仍然是$O(n^{\frac{2}{3}})$

const int N2 = 5e6+10, N3 = 2500;
int P, inv6, inv2, cnt;
int phi[N2], vis[N2], p[N2];
int S1[N2], S2[N3], v[N3];
ll N;

ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
void init(int n) {
	phi[1] = 1;
	REP(i,2,n-1) {
		if (!vis[i]) p[++cnt]=i,phi[i]=i-1;
		for (int j=1,t;j<=cnt&&i*p[j]<=n; ++j) {
			vis[t=i*p[j]]=1;
			if (i%p[j]==0) {phi[t]=phi[i]*p[j];break;}
			phi[t]=phi[i]*phi[p[j]];
		}
	}
	REP(i,1,n) S1[i]=(S1[i-1]+(ll)i*i%P*phi[i]%P)%P;
}
ll sqr(ll n) {n%=P;return n*n%P;}
ll s_1(ll n) {n%=P;return n*(n+1)%P*inv2%P;}
ll s_2(ll n) {n%=P;return n*(n+1)%P*(2*n+1)%P*inv6%P;}
ll s_3(ll n) {n%=P;return sqr(s_1(n));}
int sum(ll n) {
	if (n<N2) return S1[n]; int x = N/n;
	if (v[x]) return S2[x]; v[x] = 1;
	int &ans = S2[x] = s_3(n), mx = sqrt(n);
	REP(i,2,mx) (ans-=sqr(i)*sum(n/i)%P)%=P;
	for (ll i=mx+1,j,k=n/i; i<=n; i=j+1,--k) {
		(ans-=(s_2(j=n/k)-s_2(i-1))*sum(k)%P)%=P;
	}
	return ans;
}

int main() {
	scanf("%d%lld", &P, &N);
	inv2 = inv(2), inv6 = inv(6);
	init(N2-1);
	int mx = sqrt(N), ans = 0;
	REP(i,1,mx) (ans+=(S1[i]-S1[i-1])*s_3(N/i)%P)%=P;
	for (ll i=mx+1,j,k=N/i; i<=N; i=j+1,--k) {
		(ans+=(sum(j=N/k)-sum(i-1))*s_3(k)%P)%=P;	
	}
	if (ans<0) ans+=P;
	printf("%d\n", ans);
}

练习4. luogu P3172 [CQOI2015]选数

大意: 求选择$n$个范围[L,R]的数, 且$gcd=k$的方案数

相当于求$\sum\limits_{L\le a_1 \le R}\cdots\sum\limits_{L\le a_n \le R}[gcd=k]$

等价于$\sum\limits_{l\le a_1 \le r}\cdots\sum\limits_{l\le a_n \le r}[gcd=1]$, 其中$l=\lfloor \frac{L-1}{K}\rfloor+1, r=\frac{R}{K}$

再反演一下就没了.....$\sum\limits_{d=1}^{r}\mu(d)(\lfloor \frac{r}{d}\rfloor-\lfloor \frac{l-1}{d} \rfloor)^n$

这题因为有$\lfloor \frac{r}{d}\rfloor$和$\lfloor \frac{l-1}{d} \rfloor$两部分, 所以为了方便直接用map记忆化了

这题刚开始想错了, 反演成$\varphi$了, 结果改了半天................

const int N2 = 2e6+10, N3 = 2000, P = 1e9+7;
int mu[N2], vis[N2], p[N2];
int n, k, l, r, cnt;
unordered_map<int,int> S2;
ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} 
void init(int n) {
    mu[1] = 1;
    REP(i,2,n-1) {
        if (!vis[i]) p[++cnt]=i,mu[i]=-1;
        for (int j=1,t;j<=cnt&&i*p[j]<=n; ++j) {
            vis[t=i*p[j]]=1;
            if (i%p[j]==0) {mu[t]=0;break;}
            mu[t] = -mu[i];
        }
        mu[i] += mu[i-1];
    }
}
int sum(int n) {
    if (n<N2) return mu[n];
    if (S2.count(n)) return S2[n];
    int ans = 1, mx = sqrt(n);
    REP(i,2,mx) (ans-=sum(n/i))%=P;
    for (int i=mx+1,j,k=n/i; i<=n; i=j+1,--k) {
        (ans-=((j=n/k)-i+1)*sum(k)%P)%=P;
    }
    return S2[n]=ans;
}

int main() {
    scanf("%d%d%d%d", &n, &k, &l, &r);
    l = (l-1)/k, r /= k;
    init(N2-1);
    int ans = 0;
    for (int i=1,j; i<=r; i=j+1) {
        j = r/(r/i);
        if (l/i) j = min(j,l/(l/i));
        (ans+=(sum(j)-sum(i-1))*qpow(r/i-l/i,n)%P)%=P;
    }
    if (ans<0) ans+=P;
    printf("%d\n", ans);
}

练习5. Luogu P3327 [SDOI2015]约数个数和

大意: 求$\sum\limits_{i=1}^n\sum\limits_{j=1}^md(ij)$, $d(n)$为除数函数

对于除数函数可以这样处理$d(ij)=\sum\limits_{x|i}\sum\limits_{y|i}[x\perp y]=\sum\limits_{x|i}\sum\limits_{y|i}\sum\limits_{d|gcd(x,y)}\mu(d)$

所以就可以得到$\sum\limits_{i=1}^n\sum\limits_{j=1}^md(ij)=\sum\limits_{d=1}^{min(n,m)}\varphi(d)\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}\lfloor \frac{n}{id} \rfloor \sum\limits_{j=1}^{\lfloor \frac{m}{d}\rfloor}\lfloor \frac{m}{jd} \rfloor$

然后就没了....主要这题的数据范围太小, 不需要杜教筛也行

 

posted @ 2019-03-20 14:24  uid001  阅读(210)  评论(0编辑  收藏  举报