莫比乌斯反演网络上的其他blog已经介绍得极其详尽了,因此虽然很有价值但是不作介绍。证明的话利用了欧拉函数的一些性质,不是很困难,信息安全数学基础也讲过,因此也不介绍。重要的还是题目和函数的构造。
贴一些传送门:
https://blog.csdn.net/litble/article/details/72804050 (非常详细)
https://www.cnblogs.com/DF-yimeng/p/8490487.html
https://www.cnblogs.com/peng-ym/p/8647856.html
GCD
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 17328 Accepted Submission(s): 6679
Problem Description
Given 5 integers: a, b, c, d, k, you’re to find x in a…b, y in c…d that GCD(x, y) = k. GCD(x, y) means the greatest common divisor of x and y. Since the number of choices may be very large, you’re only required to output the total number of different number pairs.
Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.
Yoiu can assume that a = c = 1 in all test cases.
Input
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.
Output
For each test case, print the number of choices. Use the format in the example.
Sample Input
2
1 3 1 5 1
1 11014 1 14409 9
Sample Output
Case 1: 9
Case 2: 736427
Hint
For the first sample input, all the 9 pairs of numbers are (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 5), (3, 4), (3, 5).
题意非常直白,求[1,b]和[1,d]两个区间中gcd为某个值的数对总数。可以采用打因数表+容斥原理的做法做(事实上相当多莫比乌斯反演题都可以这么做,可惜的是更多的是替代不了的)
莫比乌斯反演的话,首先gcd==e就是说[1,b/e]和[1,d/e]之间互质的数对数,然后构造F函数,F(i)为[1,b/e]和[1,d/e]之间gcd=i,2i,3i……的数的对数,这个其实就是[1,p=(b/e)/i]和[1,q=(d/e)/i]之间所有数对数, 求出F(i)=p*(2*q-p+1)/2,求法是:假设p 筛莫比乌斯函数这里用到了欧拉筛法,每个数只被其最小质因子筛去,然后顺便递推。当然,采用埃氏筛暴力也是OK的,效率稍微慢一点而已。
#include
#include
using namespace std;
using LL=long long;
int T,t,b,d,e,pn,miu[100005],p[100005];
LL ans;
bool f[100005];
void sieve()
{
miu[1]=1;
p[0]=1E9;
for(int i=2,j,k;i<=1E5;i++)
{
if(!f[i])
p[++pn]=i,miu[i]=-1;
for(j=1;j<=pn&&(k=p[j]*i)<=1E5&&i%p[j-1];j++)
f[k]=true,miu[k]=-miu[i];
if(i%p[j-1]==0)
miu[p[j-1]*i]=0;
}
}
int main()
{
sieve();
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
ans=0;
scanf("%d%d%d%d%d",&t,&b,&t,&d,&e);
if(e>0)
{
b/=e,d/=e;
if(b>d)
swap(b,d);
for(int j=1,p,q;j<=b;j++)
{
p=b/j,q=d/j;
ans+=(LL)miu[j]*p*(2*q-p+1)/2;
}
}
printf("Case %d: %lld\n",i,ans);
}
return 0;
}
这类题经常还会用到一个整除分块的优化,把O(n)的效率优化为O(sqrt(n)),以上题为例,可以修改如下,其中sum是莫比乌斯函数前缀和。
#include
#include
using namespace std;
using LL=long long;
int T,t,b,d,e,pn,miu[100005],p[100005],sum[100005];
LL ans;
bool f[100005];
void sieve()
{
miu[1]=1;
p[0]=1E9;
for(int i=2,j,k;i<=1E5;i++)
{
if(!f[i])
p[++pn]=i,miu[i]=-1;
for(j=1;j<=pn&&(k=p[j]*i)<=1E5&&i%p[j-1];j++)
f[k]=true,miu[k]=-miu[i];
if(i%p[j-1]==0)
miu[p[j-1]*i]=0;
}
for(int i=1;i<=1E5;i++)
sum[i]=sum[i-1]+miu[i];
}
int main()
{
sieve();
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
ans=0;
scanf("%d%d%d%d%d",&t,&b,&t,&d,&e);
if(e>0)
{
b/=e,d/=e;
if(b>d)
swap(b,d);
for(int j=1,p,q,k;j<=b;j=k+1)
{
p=b/j,q=d/j;
k=min(b/p,d/q);
ans+=(LL)(sum[k]-sum[j-1])*p*(2*q-p+1)/2;
}
}
printf("Case %d: %lld\n",i,ans);
}
return 0;
}
原理其实很简单,就是因为求和的大部分时候p和q没有发生变化,那么我们对于p和q相同的F求一个乘积就可以一步到位了。j到k就是我们要的p和q相同的范围。(b/p)保证了p不变,(d/q)保证了q不变,可以证明p或者q的变化次数的级别是2*sqrt(n)的(或者可以画一个反比例函数,按分成x=sqrt(n)分成左右两段,左边右边的变化次数大概都是sqrt(n))。
题目描述
神犇YY虐完数论后给傻×kAc出了一题
给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对
kAc这种傻×必然不会了,于是向你来请教……
多组输入
输入输出格式
输入格式:
第一行一个整数T 表述数据组数
接下来T行,每行两个正整数,表示N, M
输出格式:
T行,每行一个整数表示第i组数据的结果
输入输出样例
输入样例#1:
2
10 10
100 100
输出样例#1:
30
2791
说明
T = 10000
N, M <= 10000000
这题就比上题麻烦不少,不过基础还是建立在上题上的,对于gcd为某个特定的质数,我们知道怎么做,那么朴素的方法就是枚举质数,多次做莫比乌斯反演,但是会超时。实际上,把式子整理整理就可以做了。
假设 n < m n<m n<m 把
a n s = ∑ i s p r i m e ( p ) n ∑ p ∣ d n ( n d ) ( m d ) μ ( d p ) ans=\sum_{isprime(p)}^n \sum_{p|d}^n ( \frac{n}{d})(\frac{m}{d})\mu(\frac{d}{p}) ans=∑isprime(p)n∑p∣dn(dn)(dm)μ(pd)
中的两个求和符号换换位置,变成
a n s = ∑ d n ( n d ) ( m d ) ∑ p ∣ d + a n d + i s p r i m e ( p ) μ ( d p ) ans=\sum_d^n ( \frac{n}{d})(\frac{m}{d}) \sum_{p|d +and+isprime(p)} \mu(\frac{d}{p}) ans=∑dn(dn)(dm)∑p∣d+and+isprime(p)μ(pd)
发现后面的一串东西可以在筛的时候统计,再求个前缀和以方便整除分块。
#include
#include
using namespace std;
using LL=long long;
int T,n,m,pn,miu[10000005],p[6500005],sum[10000005];
LL ans;
bool f[10000005];
void sieve()
{
miu[1]=1;
p[0]=1E9;
for(int i=2,j,k;i<=1E7;i++)
{
if(!f[i])
p[++pn]=i,miu[i]=-1;
for(j=1;j<=pn&&(k=p[j]*i)<=1E7&&i%p[j-1];j++)
f[k]=true,miu[k]=-miu[i];
if(i%p[j-1]==0)
miu[p[j-1]*i]=0;
}
for(int i=1;i<=pn;i++)
for(int j=p[i];j<=1E7;j+=p[i])
sum[j]+=miu[j/p[i]];
for(int i=2;i<=1E7;i++)
sum[i]+=sum[i-1];
}
int main()
{
sieve();
scanf("%d",&T);
while(T--)
{
ans=0;
scanf("%d%d",&n,&m);
if(n>m)
swap(n,m);
for(int d=1,p,q,k;d<=n;d=k+1)
{
p=n/d,q=m/d;
k=min(n/p,m/q);
ans+=(LL)p*q*(sum[k]-sum[d-1]);
}
printf("%lld\n",ans);
}
return 0;
}