莫比乌斯反演 (HDU 1695 ( GCD ))
声明:本文思路来自大佬
HDU 1695
题意:
这就需要用到莫比乌斯反演
什么是莫比乌斯反演?
个人理解:
简单来说,就是给出一个函数 F(n),然后再由F(n)定义一个函数G(n);然后已知G(n)求 F(n),就可以通过反演由G(n)反向得到F(n)。
就是根据已知的给反推回去。
我们先来看一个函数
这里 d∣n 的意思是d能整除n,也就是说第一个函数可以由他的每一个因子带入另一个函数的和而求得,那我们先写出这个函数的前几项看看
然后我们试试用F(i)来表示f(i)那么就可以用几个F(i)的加减来解决了
在反推的过程中我们其实可以得到一些规律,f(n)所对应的 F(d) 必为其的一些系数,且F(d)前面的正负号与另一个函数有关,那么我们可以假写出一个式子
可以认为f(n)为F(d)乘上一个系数而得到的,且系数可以认为是一个函数μ(d),我们写出μ(d)的前几项来看看
i: 1 2 3 4 5 6 7 8 9 10 11 12
μ(i): 1 -1 -1 0 -1 1 -1 0 0 1 -1 0
素因数 1 2 3 2*2 5 2*3 7 2*2*2 3*3 2*5 11 2*2*3
这个函数其实就是莫比乌斯函数,他有如下的定义
其中μ(d)为莫比乌斯函数,其定义如下
这样的转换就叫做莫比乌斯反演,那么如何快速的求得莫比乌斯函数呢
莫比乌斯函数有以下几个性质
一、对于任意正整数n有
二、莫比乌斯函数是积性函数
设f(n)为一个定义在N+集合上的函数,如果对于任意(x, y)=1有f(xy)= f(x)f(y),则称f(n)为一个积性函数; 若对于任意x和y均有f(xy)= f(x)f(y),则称f(n)为一个完全积性函数
由于莫比乌斯函数是一个积性函数所以我们就可以用线性筛来求得莫比乌斯函数的值了
int vis[N];
int mu[N];
int prime[N];
int cnt;
void Init(){
memset(vis,0,sizeof(vis));
mu[1] = 1;
cnt = 0;
for(int i=2; i<N; i++) {
if(!vis[i]) {
prime[cnt++] = i;
mu[i] = -1;
}
for(int 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;
}
}
}
}
那我们可以用莫比乌斯反演来解决一下这个问题
long long a,b,c,d,k;
scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
if(k==0)
{
printf("Case %d: 0\n",cas++);
continue;
}
b=b/k;
d=d/k;
long long ans1=0,ans2=0;
for(int i=1; i<=min(b,d); i++)
ans1+=mu[i]*(b/i)*(d/i);
此时的 ans1 还不是我们要的答案,此时的比如 1<=x<=3,1<=y<=4;
(x,y) => (1,1) (1,2) (1,3) (1,4) (2,1) (2,2) (2,3) (2,4) (3,1) (3,2) (3,3) (3,4)
可以发现(1,2)(2,1)(1,3)(3,1)(2,3)是重复的
这个时候就需要容斥一下
for(int i=1; i<=min(b,d); i++)//容斥的部分
ans2+=mu[i]*(min(b,d)/i)*(min(b,d)/i);
ans1-=ans2/2;
最后附上代码:
#include <bits/stdc++.h>
#define MAX_INT ((unsigned)(-1)>>1)
#define MIN_INT (~MAX_INT)
#define db printf("where!\n");
using namespace std;
#define ll long long
template<class T>inline void read(T &res){
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
const int N=100005;
int vis[N];
int mu[N];
int prime[N];
int cnt;
void Init(){
memset(vis,0,sizeof(vis));
mu[1] = 1;
cnt = 0;
for(int i=2; i<N; i++) {
if(!vis[i]) {
prime[cnt++] = i;
mu[i] = -1;
}
for(int 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()
{
Init();
int cas=1;
int t;cin>>t;
while(t--){
long long a,b,c,d,k;
scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
if(k==0)
{
printf("Case %d: 0\n",cas++);
continue;
}
b=b/k;
d=d/k;
long long ans1=0,ans2=0;
for(int i=1; i<=min(b,d); i++)
ans1+=mu[i]*(b/i)*(d/i);
for(int i=1; i<=min(b,d); i++)//容斥的部分
ans2+=mu[i]*(min(b,d)/i)*(min(b,d)/i);
ans1-=ans2/2;
printf("Case %d: %lld\n",cas++,ans1);
}
return 0;
}
关于反演入门,还有一篇文章个人觉得写得不错