莫比乌斯反演学习笔记
要说的话
有很多想说的,但不知从何说起。好久没有学数学了,整一个来记这些容易忘的东西。
Pre:整除分块
计算:
有很多题目会用到这个东西,发现\(\left\lfloor\dfrac{n}{i}\right\rfloor\)的分布呈块状,块的右端点为\(n/(n/i)\)
设当前块的左端点为\(l\) ,右端点为\(r\) ,那这一块的贡献是\(\left\lfloor\dfrac{n}{i}\right\rfloor*(r-l+1)\) ,这样就可以在\(O(\sqrt n)\)的时间内处理答案了!
𝜇
定义
首先是定义:
\(𝜇(1)=1\)
若\(n=p_1^{c_1}*p_2^{c_2}*...*p_k^{c_k}\) (p为质数)
则\(𝜇=(-1)^k\)
两个性质
第一个用得比较多。
莫比乌斯反演
式子式子式子
由:
得:
我还以为反演是什么极端高级的东西,只是...一个东西的反表示...
这个是莫比乌斯反演定理
还有一个:
由:
得:
好啦,基本的都讲完了。
一点经验
贯穿始终
把枚举约数-->转换成-->枚举约数和这个约数出现的次数
直接去掉不好枚举的\(i,j\) ,式子整理必用!!
TLE
能放到主函数的就尽量放,调用的函数越多,你的程序就越慢。
这一点会卡10分我有一题直接卡70
还有\(int\) ,\(long\) \(long\)的玄学时间
最重要的:尽量少用除法,可以用中间变量存一下结果。
一些题目
[POI2007 ZAP-Queries]
Sol
我这个傻子先做不好做的才来做的这个
式子很好推,代码很好写。
好了。
Code
#include<bits/stdc++.h>
#define ll long long
#define V (50000)
#define N (50010)
using namespace std;
int T,n,m,d,tot,used[N],mu[N],p[N];
ll sum[N];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
int main(){
mu[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) p[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*p[j]<=V;j++){
used[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else break;
}
}
for(int i=1;i<=V;i++) sum[i]=sum[i-1]+mu[i];
T=read();
while(T--){
n=read(),m=read(),d=read();
if(n>m) swap(n,m);
n/=d,m/=d;
ll res=0;
for(ll l=1,r;l<=n;l=r+1){
int x=n/l,y=m/l;
r=min(n/x,m/y);
res+=(sum[r]-sum[l-1])*x*y;
}
printf("%lld\n",res);
}
return 0;
}
[[HAOI2011] Problem b]
Sol
一个简单的变式,想象有一个存答案的表,类比一下二维前缀和。
Code
#include<bits/stdc++.h>
#define ll long long
#define V (50000)
#define N (50010)
using namespace std;
int T,n1,n2,n3,n4,d,tot,used[N],mu[N],p[N],sum[N];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline int solo(int n,int m){
int res=0;
for(int l=1,r;l<=min(n,m);l=r+1){
int x=n/l,y=m/l;
r=min(n/x,m/y);
res+=(sum[r]-sum[l-1])*x*y;
}
return res;
}
int main(){
mu[1]=1,sum[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) p[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*p[j]<=V;j++){
used[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else break;
}
sum[i]=sum[i-1]+mu[i];
}
T=read();
while(T--){
n1=read(),n2=read(),n3=read(),n4=read(),d=read();
// cout<<ans1<<" "<<ans2<<" "<<ans3<<" "<<ans4<<endl;
printf("%d\n",solo(n2/d,n4/d)+solo((n1-1)/d,(n3-1)/d)-solo(n2/d,(n3-1)/d)-solo((n1-1)/d,n4/d));
}
return 0;
}
[SDOI2014]数表
求:
Sol
完整的过程就不给了,我有几步没推好的放一下。
莫比乌斯反演后:
(除法默认下取整)
𝜇提到前面来:
在\(i \in [1,\frac{n}{d}],j \in [1,\frac{m}{d}]\)中,有\(\frac{n}{dx}*\frac{m}{dx}\)个\(𝜇(x)\)
得:
设\(T=dx\)得:
Code
#include<bits/stdc++.h>
#define lb(i) (i&(-i))
#define ll long long
#define V (100000)
#define N (100010)
#define P (1<<31)
using namespace std;
struct xbk{int n,m,k,id;}q[N];
struct xll{ll v,id;}d[N];
int T,tot,p[N],u[N];
ll c[N],dd[N],ans[N];
bool used[N];
inline int read(){
int w=0;
char ch=getchar();
bool f=0;
while(ch>'9'||ch<'0'){
if(ch=='-') f=1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return f?-w:w;
}
inline bool cmp1(xll a,xll b){return a.v<b.v;}
inline bool cmp2(xbk a,xbk b){return a.k<b.k;}
inline void oula(){
u[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) u[i]=-1,p[++tot]=i;
for(int j=1;j<=tot;j++){
if(i*p[j]>V) break;
used[i*p[j]]=1;
if(!(i%p[j])) break;
u[i*p[j]]=-u[i];
}
}
for(int i=1;i<=V;i++)
for(int j=i;j<=V;j+=i) dd[j]+=i;
//dd是σ
for(int i=1;i<=V;i++) d[i].id=i,d[i].v=dd[i];
sort(d+1,d+1+V,cmp1);
return;
}
inline ll ask(int pos){
ll res=0;
for(int i=pos;i;i-=lb(i)) res+=c[i];
return res;
}
inline void add(int pos,ll v){
for(int i=pos;i<=V;i+=lb(i)) c[i]+=v;
}
//树状数组维护𝜇σ
inline void add1(int x){
for(ll i=1;i*x<=V;i++) add(i*x,u[i]*dd[x]);
}
inline ll solo(ll nn,ll mm){
ll res=0;
if(nn>mm) swap(nn,mm);
for(int l=1,r;l<=nn;l=r+1){
r=min(nn/(nn/l),mm/(mm/l));
res+=(nn/l)*(mm/l)*(ask(r)-ask(l-1));
}
return res%P;
}
int main(){
oula();
T=read();
for(int i=1;i<=T;i++)
q[i].n=read(),q[i].m=read(),q[i].k=read(),q[i].id=i;
sort(q+1,q+1+T,cmp2);
int now=0;
for(int i=1;i<=T;i++){
while(d[now+1].v<=q[i].k&&now<V) add1(d[++now].id);
ans[q[i].id]=solo(q[i].n,q[i].m);
}
for(int i=1;i<=T;i++) printf("%lld\n",ans[i]);
return 0;
}
[LuoguP2257 YY的GCD]
求:
Sol
直接推(pm指质数集)。
经典操作:
设\(T=dk\)
前缀和维护\(\mu\) ,整除分块维护前面的就好啦。
Code
#include<bits/stdc++.h>
#define ll long long
#define V (10000000)
#define N (10000010)
using namespace std;
int T,n,m,tot,used[N],mu[N],p[N];
ll sum[N];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
int main(){
mu[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) p[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*p[j]<=V;j++){
used[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else break;
}
}
for(int i=1;i<=tot;i++)
for(int j=p[i];j<=V;j+=p[i]) sum[j]+=mu[j/p[i]];
for(int i=2;i<=V;i++) sum[i]+=sum[i-1];
T=read();
while(T--){
n=read(),m=read();
if(n>m) swap(n,m);
ll res=0;
for(ll l=1,r;l<=n;l=r+1){
int x=n/l,y=m/l;
r=min(n/x,m/y);
res+=(sum[r]-sum[l-1])*x*y;
}
printf("%lld\n",res);
}
return 0;
}
[LuoguP2398 GCD SUM]
Sol
这道题看着简单,甚至只有蓝题难度 ,但我觉得确实很有水平。正是:
所以这道题就是个烂题,但它还是非常的好。
By 神仙学长gzy
推式子:
套路枚举\(gcd\) :
设\(T=dk\)
后面那一块为\(\phi(T)\)
为什么?狄利克雷卷积告诉我们的。
然后就可以整除分块了。
Code
#include<bits/stdc++.h>
#define V (100000)
#define N (100010)
#define ll long long
using namespace std;
int n,tot;
ll ans,phi[N],p[N];
bool used[N];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline void oula(){
phi[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) p[++tot]=i,phi[i]=i-1;
for(int j=1;j<=tot&&p[j]*i<=V;j++){
used[i*p[j]]=1;
if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
else{
phi[i*p[j]]=phi[i]*p[j];
break;
}
}
}
for(int i=1;i<=V;i++) phi[i]+=phi[i-1];
return;
}
int main(){
n=read();
oula();
for(int l=1,r;l<=n;l=r+1){
ll now=n/l;
r=n/now;
ans+=now*now*(phi[r]-phi[l-1]);
}
printf("%lld\n",ans);
return 0;
}
[Luogu P5521 Product]
Sol
累乘转指数挺巧妙的。
设左边的为\(ans_1\) ,右边的为\(ans_2\) :
好啦。
Code
#include<bits/stdc++.h>
#define N (1000010)
#define V (1000000)
using namespace std;
const int P=104857601;
int n,p[500010],phi[N];
bool used[N];
inline int ksm(int a,int b){
int res=1;
while(b){
if(b&1) res=1ll*res*a%P;
a=1ll*a*a%P;
b>>=1;
}
return res;
}
int main(){
scanf("%d",&n);
phi[1]=1;
for(int i=2;i<=V;i++){
if(!used[i]) p[++p[0]]=i,phi[i]=i-1;
for(int j=1;j<=p[0]&&i*p[j]<=V;j++){
used[i*p[j]]=1;
if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
else{
phi[i*p[j]]=phi[i]*p[j];
break;
}
}
}
int res0=1,res1=1;
for(int i=1;i<=V;i++) phi[i]=((phi[i]<<1)%(P-1)+phi[i-1])%(P-1);
for(int i=2;i<=n;i++) res0=1ll*res0*i%P;
res0=ksm(res0,n+n);
for(int i=2;i<=n;i++) res1=1ll*res1*ksm(i,phi[n/i]-1)%P;
res1=ksm(res1,P-2);
int res=1ll*res0*res1%P*res1%P;
printf("%d\n",res);
return 0;
}