莫比乌斯反演 习题

Tips:本文默认 nm,ab

[POI2007] ZAP-Queries

题目传送门

题意

给出 a,b,d,求满足 1xa1yb,且 gcd(x,y)=d 的二元组 (x,y) 的数量。

1n5×1041da,b5×104

解题思路

  • 原题要求的是

    i=1aj=1b[gcd(i,j)=d]

  • i,j 除以 d

    =i=1adj=1bd[gcd(i,j)=1]

  • 由莫比乌斯反演得

    =i=1adj=1bdk|gcd(i,j)μ(k)

  • 再将 i,j 除以 k,这样就不关心 k 是否为 i,j 的约数,可以直接枚举 k

    =k=1adμ(k)akdbkd

  • 这个式子的后半部分可以整除分块来优化,前半部分预处理前缀和即可。在代码实现时,可以预先让 a,b 同除以 d,方便实现

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=50010;

int T,a,b,d,n,m;
int prime[N],v[N],tot,mu[N],sum[N];
LL ans;

void primes()
{
	mu[1]=1;
	for(int i=2; i<=50000; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mu[i]=-1;
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>50000/i)
				break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				break;
			mu[prime[j]*i]=-mu[i];
		}
	}
	
	for(int i=1; i<=50000; i++)
		sum[i]=sum[i-1]+mu[i];
}
 	
int main()
{
	primes();
	
    scanf("%d",&T);
    while(T--)
    {
    	scanf("%d%d%d",&a,&b,&d);
    	n=a/d;  m=b/d;
    	if(n>m)
    		swap(n,m);
    	
    	ans=0;
    	for(int l=1,r; l<=n; l=r+1)
    	{
    		r=min(n/(n/l),m/(m/l));
    		ans+=1LL*(sum[r]-sum[l-1])*1LL*(n/l)*1LL*(m/l);
		}
		
		printf("%lld\n",ans);
	}
    
    return 0;
}

[HAOI2011] Problem b

题目传送门

题意

对于给出的 n 个询问,每次求有多少个数对 (x,y),满足 axbcyd,且 gcd(x,y)=kgcd(x,y) 函数为 xy 的最大公约数。

1n,k5×1041ab5×1041cd5×104

解题思路

  • 类比上面一题 ZAP,设 f(n,m) 表示满足 1xn1ym,且 gcd(x,y)=k 的二元组 (x,y) 的数量

  • 类似二维前缀和,容易得出

    ans=f(b,d)f(a1,d)f(b,c1)+f(a1,c1)

  • 同样,可以一开始先令 a,b,c,d 除以 k,方便代码实现

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=50010;

int T,a,b,c,d,k;
int prime[N],v[N],tot,mu[N],sum[N];
LL ans;

void primes()
{
	mu[1]=1;
	for(int i=2; i<=50000; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mu[i]=-1;
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>50000/i)
				break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				break;
			mu[prime[j]*i]=-mu[i];
		}
	}
	
	for(int i=1; i<=50000; i++)
		sum[i]=sum[i-1]+mu[i];
}

LL f(int n,int m)
{
	if(n>m)
		swap(n,m);
	LL res=0;
	for(int l=1,r; l<=n; l=r+1)
   	{
    	r=min(n/(n/l),m/(m/l));
    	res+=1LL*(sum[r]-sum[l-1])*1LL*(n/l)*1LL*(m/l);
	}
	
	return res;
}
 	
int main()
{
	primes();
	
    scanf("%d",&T);
    while(T--)
    {
    	scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    	
    	ans=0;
    	ans=f(b/k,d/k)-f((a-1)/k,d/k)-f(b/k,(c-1)/k)+f((a-1)/k,(c-1)/k);
		
		printf("%lld\n",ans);
	}
    
    return 0;
}

YY的GCD

题目传送门

题意

T 组数据,每组数据给定 N,M,求 1xN1yMgcd(x,y) 为质数的 (x,y) 有多少对。

T=104N,M107

解题思路

  • primes 表示质数集合

  • 原题要求的是

    ans=kprimesi=1nj=1m[gcd(i,j)=k]

  • 同除以 k 得:

    =kprimesi=1nkj=1mk[gcd(i,j)=k]

  • 由莫比乌斯反演得

    =kprimesi=1nkj=1mkd|gcd(i,j)μ(d)

  • 我们枚举 d 得:

    =kprimesd=1nkμ(d)nkdmkd

  • T=kd,把 T 提到前面去

    =T=1nnTmTk|T,kprimesμ(Tk)

  • 对于后半部分式子,我们可以在线性筛后预处理出来,再做一次整除分块即可求出答案

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=10000010;

int T,n,m;
int prime[N],v[N],tot,mu[N];
LL sum[N],g[N];

void primes()
{
	mu[1]=1;
	for(int i=2; i<=1e7; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mu[i]=-1;
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>1e7/i)
				break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				break;
			mu[prime[j]*i]=-mu[i];
		}
	}
	
	for(int i=1; i<=tot; i++)
	{
		for(int j=1; j*prime[i]<=1e7; j++)
			g[prime[i]*j]+=mu[j]; 
	}
	
	for(int i=1; i<=1e7; i++)
		sum[i]=sum[i-1]+g[i];
}

LL cacl(int n,int m)
{
	if(n>m)
		swap(n,m);
	
	LL res=0; 
	for(int l=1,r; l<=n; l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		res+=1LL*(sum[r]-sum[l-1])*(n/l)*(m/l);
	}
	
	return res;
}
 	
int main()
{
	primes();
	
    scanf("%d",&T);
    while(T--)
    {
    	scanf("%d%d",&n,&m);
		printf("%lld\n",cacl(n,m));
	}
    
    return 0;
}

于神之怒加强版

题目传送门

题意

给定 n,m,k,计算

i=1nj=1mgcd(i,j)k

109+7 取模的结果。

对于全部的测试点,保证 1T2×1031n,m,k5×106

解题思路

  • 直接开始推式子

    ans=d=1ni=1nj=1mdk[gcd(i,j)=d]

  • 除以 d 得:

    =d=1ni=1ndj=1mddk[gcd(i,j)=1]

    =d=1ndki=1ndj=1mdx|gcd(i,j)μ(x)

  • 枚举 x 得:

    d=1ndkx=1ndμ(x)ndxmdx

  • 类似上一题的优化,设 T=dx 得:

    T=1nnTmTd|Tdkμ(Td)

  • 观察到,d|Tdkμ(Td)狄利克雷卷积的形式,而 f(d)=dkμ(Td) 又是 积性函数,所以 g(T)=d|Tdkμ(Td) 也是积性函数。因此,我们可以考虑线性筛预处理出 g 的值

  • T 质因数分解为 T=i=1qpici,则

    g(T)=i=1qg(pici)

    =i=1qd|picidkμ(picid)

  • 观察到,当 d=1,pi,pi2pici2 时,μ(picid)=0,所以

    g(T)=i=1qpi(ci1)k×μ(pi)+picik×μ(1)

    =i=1qpi(ci1)k×(pik1)

  • 这个式子是可以预处理出来的

  • 所以

    ans=T=1nnTmTg(T)

  • 利用整除分块即可求解

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=5000010;
const int MOD=1e9+7;

int T,k,n,m;
int prime[N],v[N],tot;
LL g[N],mi[N],sum[N];

LL ksm(int a,int b)
{
	if(b==0)
		return 1;
	if(b==1)
		return a;
		
	LL tmp=ksm(a,b/2);
	if(b%2==0)
		return (tmp*tmp)%MOD;
	else
		return ((tmp*tmp)%MOD*(LL)a)%MOD;
}

void primes()
{
	g[1]=1;
	for(int i=2; i<=5e6; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mi[i]=ksm(i,k);
			g[i]=(mi[i]-1+MOD)%MOD; //i为质数时g(i)=i^k-1
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>5e6/i)
				break;
			v[i*prime[j]]=prime[j];
			
			if(i%prime[j]==0)
				g[i*prime[j]]=(g[i]*mi[prime[j]])%MOD; //当i和prime[j]不互质时,读者可自行证明该结论正确性
			else
				g[i*prime[j]]=(g[i]*g[prime[j]])%MOD;
		}
	}
	
	for(int i=1; i<=5e6; i++)
		sum[i]=sum[i-1]+g[i];
}

LL cacl(int n,int m)
{
	if(n>m)
		swap(n,m);
	
	LL res=0; 
	for(int l=1,r; l<=n; l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		res=(res+((sum[r]-sum[l-1]+MOD)%MOD)*(n/l)%MOD*(m/l)%MOD)%MOD;
	}
	
	return res;
}
 	
int main()
{
	scanf("%d%d",&T,&k);
	
	primes();
	
    while(T--)
    {
    	scanf("%d%d",&n,&m);
		printf("%lld\n",cacl(n,m));
	}
    
    return 0;
}

[国家集训队] Crash的数字表格 / JZPTAB

题目传送门

题意

给定 n,m,计算

i=1nj=1mlcm(i,j)

1n,m107

解题思路

  • lcm(i,j) 变换一下得:

    ans=i=1nj=1mijgcd(i,j)

  • 枚举 gcd(i,j) 得:

    =i=1nj=1md=1nijd[gcd(i,j)=d]

  • 除以 d 得:

    =d=1ni=1ndj=1mdij×d[gcd(i,j)=1]

    =d=1ndi=1ndj=1mdij[gcd(i,j)=1]

  • 把后半部分式子单独提出来,设:

    f(a,b)=i=1aj=1bij[gcd(i,j)=1]

  • 化简得:

    f(a,b)=i=1aj=1bijk|gcd(i,j)μ(k)

    =k=1aμ(k)i=1akj=1bkij×k2

    =k=1aμ(k)×k2i=1akj=1bkij

  • g(a,b)=i=1aj=1bij

  • 化简得:

    =(1+a)a2×(b+1)b2

  • 所以,求出 g(a,b) 的时间复杂度为 O(1)

  • g(a,b) 代回 f(a,b) 中得:

    f(a,b)=k=1aμ(k)×k2×g(ak,bk)

  • 我们利用整除分块即可求出 f(a,b)

  • f(a,b) 代回 ans 得:

    ans=d=1nd×f(nd,md)

  • 再做一次整除分块即可求解

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=10000010;
const int MOD=20101009;

int n,m;
int prime[N],v[N],tot,mu[N];
LL sum[N];

void primes()
{
	mu[1]=1;
	for(int i=2; i<=1e7; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mu[i]=-1;
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>1e7/i)
				break;
			v[i*prime[j]]=prime[j];
			if(i%prime[j]==0)
				break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	
	for(int i=1; i<=1e7; i++)
		sum[i]=(sum[i-1]+1LL*(mu[i]+MOD)%MOD*i%MOD*i%MOD)%MOD;
}

LL g(int a,int b)
{
	return (1LL*a*(a+1)/2%MOD)*(1LL*b*(b+1)/2%MOD)%MOD;
}

LL f(int a,int b)
{
	if(a>b)
		swap(a,b);
		
	LL res=0;
	for(int l=1,r; l<=a; l=r+1)
	{
		r=min(a/(a/l),b/(b/l));
		res=(res+(sum[r]-sum[l-1]+MOD)%MOD*g(a/l,b/l)%MOD)%MOD;
	}
	
	return res;
}

LL calc(int a,int b)
{
	if(a>b)
		swap(a,b);
		
	LL res=0;
	for(int l=1,r; l<=a; l=r+1)
	{
		r=min(a/(a/l),b/(b/l));
		res=(res+1LL*(r-l+1)*(l+r)/2%MOD*f(a/l,b/l)%MOD)%MOD;
	}
	
	return res;
}
 		
int main()
{
	primes();
	
	scanf("%d%d",&n,&m);
	printf("%lld",calc(n,m));
    
    return 0;
}

[SDOI2014] 数表

题目传送门

题意

有一张 n×m 的数表,其第 i 行第 j 列(1in1jm)的数值为能同时整除 ij 的所有自然数之和。给定 a,计算数表中不大于 a 的数之和。

1n,m1051Q2×104

解题思路

  • f(x) 表示 x 的因数之和

  • ans=i=1nj=1mf(gcd(i,j))×[f(gcd(i,j))a]

  • 枚举 gcd(i,j) 得:

    =d=1ni=1nj=1mf(d)×[gcd(i,j)=d]×[f(d)a]

    =d=1nf(d)×[f(d)a]i=1ndj=1md[gcd(i,j)=1]

  • 由莫比乌斯反演得:

    =d=1nf(d)×[f(d)a]i=1ndj=1mdk|gcd(i,j)μ(k)

    =d=1nf(d)×[f(d)a]k=1ndμ(k)nkdmkd

  • 套路设 T=kd 得:

    =T=1nnTmTx|Tμ(Tx)×f(x)×[f(x)a]

  • f(x) 可以在 O(nlnn) 的时间预处理出来

  • 因为有 a 的限制,当 a 变大时,后半部分式子也在改变。所以考虑将询问离线,按 a 的值从小到大排序,每次 a 变大时都类似插入操作,考虑用树状数组维护

  • g(T)=x|Tμ(Tx)×f(x),对于所有 f(x)a 来说,它会对 g(x),g(2x) 产生贡献,所以给每个 g 都加上 μ(Tx)×f(x) 即可

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=100010,Q=20010;
const LL MOD=2147483648;

struct node
{
	int dat;
	int id;
}f[N];
struct que
{
	int n,m,s,id;
}a[Q];

int T;
int prime[N],v[N],tot,mu[N];
LL c[N],ans[N];

bool cmp(que xx,que yy)
{
	return xx.s<yy.s;
}

bool cmp2(node xx,node yy)
{
	return xx.dat<yy.dat;
}

void add(int x,LL y)
{
	for(; x<=1e5; x+=(x&-x))
		c[x]=(c[x]+y)%MOD;
}

LL ask(int x)
{
	LL s=0;
	for(; x; x-=(x&-x))
		s=(c[x]+s)%MOD;
	return s;
}

void update(int x,LL y)
{
	for(int i=1; i*x<=1e5; i++)
		add(i*x,1LL*mu[i]*y);
}

void primes()
{
	mu[1]=1;
	for(int i=2; i<=1e5; i++)
	{
		if(!v[i])
		{
			v[i]=i;
			prime[++tot]=i;
			mu[i]=-1;
		}
			
		for(int j=1; j<=tot; j++)
		{
			if(prime[j]>v[i] || prime[j]>1e5/i)
				break;
			v[i*prime[j]]=prime[j];
			
			if(i%prime[j]==0)
				break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	
	for(int i=1; i<=1e5; i++)
		for(int j=1; i*j<=1e5; j++)
			f[i*j].dat=(f[i*j].dat+i)%MOD;
		
	for(int i=1; i<=1e5; i++)
		f[i].id=i;
		
	sort(f+1,f+1+100000,cmp2);
}

LL calc(int a,int b)
{
	if(a>b)
		swap(a,b);
		
	LL res=0;
	for(int l=1,r; l<=a; l=r+1)
	{
		r=min(a/(a/l),b/(b/l));
		res=(res+(ask(r)-ask(l-1)+MOD)%MOD*1LL*(a/l)%MOD*(b/l)%MOD)%MOD;
	}
	
	return res;
}
 		
int main()
{
	primes();

	scanf("%d",&T);
	for(int i=1; i<=T; i++)
		scanf("%d%d%d",&a[i].n,&a[i].m,&a[i].s),a[i].id=i;
	
	sort(a+1,a+1+T,cmp);
	
	int p=1;
	for(int i=1; i<=T; i++)
	{
		while(f[p].dat<=a[i].s && p<=1e5)
			update(f[p].id,f[p].dat),p++;
		ans[a[i].id]=calc(a[i].n,a[i].m);
	}
	
	for(int i=1; i<=T; i++)
		printf("%lld\n",ans[i]);
    
    return 0;
}
posted @   xishanmeigao  阅读(12)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示