莫比乌斯反演
莫比乌斯反演
形式 1 (卷积法证
如果有
有如下反演:
证明如下(用狄利克雷卷积易证):
证毕。
形式2 (带入法证
莫比乌斯反演第二种形式
证明如下:
将
由于
(解释一下就是我们注意到t的取值是nk的倍数,且k是从1至无穷大,也就是说t在满足是n的倍数的同时要满足k的倍数,那么把k甩出去后考虑当t = a * n时有哪些k满足
好了现在当且仅当t/n为1时
证毕
一些基础转化:
求证
其中E为原函数(只有1才为1,其余为0)
证明如下:
因为
当
设
证毕
板题大赏
板题1 [POI2007]ZAP-Queries
方法1 强行推式子 + 卷积基本性质
题目相当与求
第一步把k提出来
第二步由
注意到枚举i,j再来算gcd太假,于是考虑枚举gcd,直接计算有多少个i,j满足
观察下式子,我们发现右侧
方法2 莫比乌斯反演
我们要求的式子设为
那还有什么说的,莫比乌斯反演形式2,
将F函数带入,设k=dd/n:
因为答案即为
整数分块即可。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=1e5+5;
int cntp,n,m,T,u[M],pri[M],vis[M],s[M];
inline void pre(){
u[1]=1;u[2]=-1;
for(int i=2;i<=100000;i++){
if(!vis[i]){
pri[++cntp]=i;u[i]=-1;
}
for(int j=1;j<=cntp&&i*pri[j]<=100000;j++){
vis[pri[j]*i]=1;
if(i%pri[j]==0) u[i*pri[j]]=0;
else u[i*pri[j]]=u[i]*-1;
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=100000;i++) s[i]=s[i-1]+u[i];
}
signed main(){
pre();
cin>>T;
while(T--){
int a,b,k,ans=0;
scanf("%lld%lld%lld",&a,&b,&k);
int lim=min(a/k,b/k);
a=a/k,b=b/k;int E;
for(int S=1;S<=lim;S=E+1){
E=min(a/(a/S),b/(b/S));
ans+=(s[E]-s[S-1])*(a/S)*(b/S);
}cout<<ans<<"\n";
}
return 0;
}
一点拓展
如何题目要求
考虑简单容斥,
于是就有了这道题:
[HAOI2011]Problem b
成功双倍经验
板题2 YY的GCD
我们现在多了个限制,只要
即求
同上一道题,我们先试图化简式子,
现在看似差不多化成最简式了,现在考虑如何搞这个质数。
我们希望把枚举
那我们搞一个
于是原式等于:
关于这个变换,每一个合法的k,p都对应一组d,p,且恰好覆盖。
然后将剩下的棘手的p的项合并
现在只要把f筛出来就结束了。观察
具体分析如下(实在敲不动latex了,随便找了一份)
)
其实就是按照内容分三类分析即可。
code
#include<bits/stdc++.h>
using namespace std;
inline int getint(){
int summ=0,f=1;char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')f=-1,ch=getchar();
for(;isdigit(ch);ch=getchar()) summ=(summ<<3)+(summ<<1)+ch-48;
return summ*f;
}
const int M=1e7;
int cntp,n,m,T,u[M+5],pri[M+5],vis[M+5],s[M+5],f[M+5];
inline void pre(){
u[1]=1;u[2]=-1;
for(int i=2;i<=M;i++){
if(!vis[i]){
pri[++cntp]=i;u[i]=-1;f[i]=1;
}
for(int j=1;j<=cntp&&i*pri[j]<=M;j++){
vis[pri[j]*i]=1;
if(i%pri[j]==0) u[i*pri[j]]=0,f[i*pri[j]]=u[i];
else u[i*pri[j]]=u[i]*-1,f[i*pri[j]]=-f[i]+u[i];
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=M;i++) s[i]=s[i-1]+f[i];
}
signed main(){
pre();
cin>>T;
while(T--){
int a,b,k;long long ans=0;
a=getint();b=getint();
int E;
for(int S=1;S<=min(a,b);S=E+1){
E=min(a/(a/S),b/(b/S));
//cout<<s[E]<<" "<<s[S-1]<<" "<<((a/S)*(b/S))<<endl;
ans+=1ll*(s[E]-s[S-1])*1ll*(a/S)*(b/S);
}cout<<ans<<"\n";
}
return 0;
}
[SDOI2015]约数个数和
题目要求:
我们知道一组因子个数为
那如果加一维呢,
但是这样做显然有重复。
由于
我们构造一种分配方式,若i能提供幂则只给i,若不能提供则只由j提供差值。这种情况下
每一个幂的数量都有且对应一个方案,不会重复,于是只要不取不互质的因数组成的数对有且对应一种合法因数。
于是有
至此d函数被搞成了我们喜闻乐见的gcd函数
begin to transform
change the order
continue to change the order
后面的东西可以预处理,又变回了整数分块的情况,
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,ans;
int s[50005],p[50005],mo[50005],mos[50005];
void build()
{
for(int i=1;i<=50000;i++)
mo[i]=1;
for(int i=2;i<=50000;i++)
{
if(p[i]) continue;
mo[i]=-1;
for(int j=i*2;j<=50000;j+=i)
{
p[j]=1;
if((j/i)%i==0) mo[j]=0;
else mo[j]*=-1;
}
}
for(int i=1;i<=50000;i++)
{
for(int l=1,r;l<=i;l=r+1)
{
r=(i/(i/l));
s[i]+=((r-l+1)*(i/l));
}
}
for(int i=1;i<=50000;++i) mos[i]=mos[i-1]+mo[i];
}
signed main()
{
build();
int T;
cin>>T;
while(T--)
{
ans=0;
scanf("%lld%lld",&n,&m);
int mi=min(n,m);
for(int r,l=1;l<=mi;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(mos[r]-mos[l-1])*(s[n/l]*s[m/l]);
}
cout<<ans<<endl;
}
return 0;
}
例题4 P5221 Product
搞成只有gcd后,把gcd分一边,其他一次项丢一边。(累乘可以分到分子分母算)
得
对于一个确定的
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,mod=104857601;
int pri[N/10],mu[N],n,m,tot,ans=1;
bool vis[N];
inline void Pre(){
mu[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) pri[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&pri[j]*i<=n;j++){
vis[i*pri[j]]=true;
mu[i*pri[j]]=-mu[i];
if(i%pri[j]==0){
mu[i*pri[j]]=0;break;
}
}
}
for(int i=1;i<=n;i++) mu[i]+=mu[i-1];
}
inline int ksm(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod;y>>=1;
}
return res;
}
inline int calc(int up){
int res=0;
for(int l=1,r;l<=up;l=r+1){
r=n/(n/l);
res=(res+1ll*(mu[r]-mu[l-1]+mod-1)*(up/l)%(mod-1)*(up/l)%(mod-1))%(mod-1);
}
return res;
}
int main(){
cin>>n;Pre();
for(int i=1;i<=n;i++) ans=1ll*ans*i%mod;
ans=ksm(ans,2*n);int ll=1,rr=1,c1=2,c2=2;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
while(c1<=l-1){
ll=1ll*ll*c1%mod;
c1++;
}
while(c2<=r){
rr=1ll*rr*c2%mod;
c2++;
}
ans=1ll*ans*ksm(1ll*ll*ksm(rr,mod-2)%mod,2*calc(n/l)%(mod-1))%mod;
}
cout<<1ll*(ans+mod)%mod<<endl;
return 0;
}
例题5 P3768 简单的数学题
推式子时间
在这只列出关键步骤。
其中
改变累加方式,注意到有多处可以用
设
有卷积知识
设
整数分块加杜教筛搞定。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=5e6+5,up=5e6;
map <int,int> mp;
int pri[M],vis[M],phi[M],mod,n,tot,s[M];
inline int Ksm(int x,int y){
int res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod;y>>=1;
}
return res;
}
void Pre(){
phi[1]=1;
for(int i=2;i<=up;i++){
if(!vis[i]){
pri[++tot]=i;phi[i]=i-1;
}
for(int j=1;j<=tot&&i*pri[j]<=up;j++){
vis[i*pri[j]]=1;phi[i*pri[j]]=phi[i]*phi[pri[j]];
if(i%pri[j]==0){
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
}
}
for(int i=1;i<=up;i++) s[i]=(s[i-1]+phi[i]*i%mod*i)%mod;
}
int inv6,inv2;
inline int G(int x){
int y=x%mod*(x+1)%mod*inv2%mod;
return y*y%mod;
}
inline int H(int x){
return x%mod*(x+1)%mod*(2*x+1)%mod*inv6%mod;
}
int Get_phi(int x){
if(x<=up) return s[x];
if(mp[x]) return mp[x];
int ans=G(x%mod),res=0;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
res=(res+(H(r%mod)-H((l-1)%mod))%mod*Get_phi(x/l))%mod;
}
return mp[x]=(ans-res)%mod;
}
signed main(){
cin>>mod>>n;
inv6=Ksm(6,mod-2);inv2=Ksm(2,mod-2);
Pre();int ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans=(ans+((Get_phi(r)-Get_phi(l-1))%mod*G(n/l%mod)))%mod;
}
cout<<(ans+mod)%mod;
return 0;
}
例题6 Crash的数字表格
注意到后面的数值仅仅
进行一波反演:
变换一下:
注意到给定d,后面那一坨与
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=20101009,up=1e7;
int mu[up+5],vis[up+5],pri[up],cnt,n,m,ans;
void Build(){
mu[1]=1;
for(register int i=2;i<=up;i++){
if(!vis[i]) pri[++cnt]=i,mu[i]=-1;
for(register int j=1;j<=cnt&&pri[j]*i<=up;j++){
vis[pri[j]*i]=1;mu[i*pri[j]]=mu[i]*mu[pri[j]];
if(i%pri[j]==0){
mu[i*pri[j]]=0;break;
}
}
}
for(int i=1;i<=up;i++) mu[i]=(mu[i-1]+mu[i]*i%mod*i)%mod;
}
inline int F(int l,int r){
return (l*(l+1)/2%mod)*(r*(r+1)/2%mod)%mod;
}
inline int calc(int l,int r){
int res=0;
for(int ll=1,rr;ll<=min(l,r);ll=rr+1){
rr=min(l/(l/ll),r/(r/ll));
res=(res+F(l/ll,r/ll)*(mu[rr]-mu[ll-1]))%mod;
}
return res;
}
signed main(){
Build();
cin>>n>>m;
for(int ll=1,rr;ll<=min(n,m);ll=rr+1){
rr=min(n/(n/ll),m/(m/ll));
ans=(ans+(ll+rr)*(rr-ll+1)/2%mod*calc(n/ll,m/ll))%mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}