对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d
对于这么一道题目 典型的莫比乌斯反演题
代码如下
#include
#define ll long long
using namespace std;
const int maxn=1e5+7;
bool vis[maxn];
ll prime[maxn],mu[maxn];
ll cnt;
void Init()
{
ll N=maxn;
mu[1]=1;
cnt=0;
for(ll i=2; i<N; i++)
{
if(!vis[i])
{
prime[cnt++]=i;
mu[i]=-1;
}
for(ll j=0;j<cnt&&i*prime[j]<N; j++)
{
vis[i*prime[j]]=1;
if(i%prime[j])
mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]] = 0;
break;
}
}
}
}//打表确定莫比乌斯函数值
int main()
{
ll a,b,c,d,k;
ll T,Case=0;
Init();
cin>>T;
while(T--)
{
cin>>b>>d>>k;
b/=k,d/=k;//求[1,b]中与[1,d]中gcd(x,y)=k的个数 即gcd(x/k,y/k)=1的个数 又x属于[1,b]y属于[1,d],相当于求[1,b/k]中与[1,d/k]中gcd(x,y)=1的个数
//对b,d的值更新一下,题目就转换为求[1,b] [1,d]中gcd(x,y)=1的个数
ll ans1=0,ans2=0;
for(ll i=1;i<=min(b,d);i++)
{
ans1+=mu[i]*(b/i)*(d/i);//由公式算出的包含重复部分的个数,重复部分只可能出现在两区间相交部分,所以让较小区间自身计算
}
for(ll i=1;i<=min(b,d);i++)
{
ans2+=mu[i]*(min(b,d)/i)*(min(b,d)/i);//由较小区间自身计算,其中有一半为重复 有时有的题目不需要去重,此处去重了
}
cout<<ans1-ans2/2<<endl;
}
return 0;
}
## 数论分块模板
就是在前面基础上加了个求前缀和,然后主函数中的部分变一下
#include
#include
#define ll long long
using namespace std;
const int maxn=50001;
bool vis[maxn];
ll prime[maxn],mu[maxn],sum[maxn];
ll cnt;
void getmu()
{
ll N=maxn;
mu[1] = 1;
cnt=0;
for(ll i=2;i<maxn;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
}
for(ll j=1;j<=cnt;j++)
{
if(i*prime[j]>N) break;
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<maxn;i++)
sum[i]=sum[i-1]+mu[i];//sum[i]为莫比乌斯函数的前缀和
}//打表确定莫比乌斯函数值
int main()
{
int a,b,c,d,k;
int T;
getmu();
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&b,&d,&k);
b/=k,d/=k;
int e=min(b,d);
ll ans=0;
for(int i=1,r;i<=e;i=r+1)
{
r=min(b/(b/i),d/(d/i));
ans+=(sum[r]-sum[i-1])*(b/i)*(d/i);
}
printf("%lld\n",ans);
}//数论分块模板
return 0;
}
再放一道莫比乌斯反演+数论分块+容斥定理题
题目:给定a<=x<=b , c<=y<=d 且gcd(x,y)=k,输入abcdk 求有多少对(x,y)满足
代码如下
#include
#include
#include
#include
#define maxn 50005
using namespace std;
int a,b,c,d,k;
int mu[maxn],sum[maxn],prime[maxn];
bool flag[maxn];
void getmu(){
mu[1]=1;
for(int i=2;i<maxn;i++)
{
if(!flag[i])
{
prime[++prime[0]]=i;
mu[i]=-1;
}
for(int j=1;j<=prime[0];j++)
{
if(i*prime[j]>maxn)break;
flag[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<maxn;i++)
sum[i]=sum[i-1]+mu[i];
}
int solve(int b,int d)
{
if(b>d)
swap(b,d);
b/=k;
d/=k;
int ans=0;
for(int i=1,r;i<=b;i=r+1)
{
r=min(b/(b/i),d/(d/i));
ans+=(sum[r]-sum[i-1])*(b/i)*(d/i);
}
return ans;
}
int main()
{
int n;
getmu();
scanf("%d",&n);
while(n--)
{
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("%d\n",solve(b,d)-solve(a-1,d)-solve(c-1,b)+solve(a-1,c-1));
}
}
给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对
若是直接for循环遍历所有素数prime[i],每次求gcd=prime[i] 那么可能会T。
此处运用一个高效解法
#include
#include
#include
#include
#define ll long long
using namespace std;
const int maxn=1e7+7;
bool vis[maxn];
int prime[maxn];
ll mu[maxn];
int cnt;
ll sum[maxn],f[maxn];
void Init()
{
mu[1]=1;
cnt=0;
for(int i=2;i<maxn;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*prime[j]<maxn;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j])
mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
for(int i=1;i<=cnt;i++)
{
for(int j=1;prime[i]*j<=maxn;j++)
{
f[j*prime[i]]+=mu[j];
}
}
for(int i=1;i<maxn;i++)
sum[i]=sum[i-1]+f[i];
}
int main()
{
int T;
int n,m,r;
ll ans;
Init();
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
ans=0;
for(int j=1;j<=min(n,m);j=r+1)
{
r=min(n/(n/j),m/(m/j));
ans+=1ll*(sum[r]-sum[j-1])*(n/j)*(m/j);
}
printf("%lld\n",ans);
}
return 0;
}