思路:
想了两个小时才想出容斥的思路…(真是太弱了
AC之后搜题解发现是一个莫比乌斯的入门题,准备最近抽时间学习一下AC之后再来补上莫比乌斯的思路(占坑(更新见下
容斥:
此题可以等价为:
从【1,n】中选出一个 X
从【1,m】中选出一个 Y
问有多少对不重复的 X,Y 满足 GCD(X,Y) == K?
首先 我们可以从上述问题中提取出以下条件:
1 .$ 1 <= X <= n$ 且 X = a * k
2. $ 1 <= Y <= m$ 且 Y = b * k
3.
g
c
d
(
a
,
b
)
=
=
1
gcd(a,b) == 1
gcd(a,b)==1
故我们可以枚举 a ,然后对于每一个 a 去查询 有多少个b能与其配对
而
1
<
=
b
<
=
m
/
k
1<=b <= m/k
1<=b<=m/k
故问题便转化成了经典的区间容斥问题:
在【1,m/k】中 有多少个数b 与已知的数a 互质。
此时我们发现会有重复的情况,举个例子
对于【1,5】中,我们发现:
当a = 2时,b = 1,3,5
当a = 3时,b = 1,2,4,5
此时 a = 2 ,b =3 和 a = 3,b = 2构成了重复
去重的方法很简单,我们只需要保证每次计算的(a,b)都满足 a < b
故我们需要减掉【1,m/k】中与a互质又小于a的b的数量。
很显然,这就是求 a的欧拉函数。
最后再加上一个1即可,因为 1是与任何a都互质的,但每个数都没有算上他,故最后应该加上一个1.
故 容斥 + 欧拉函数 可解。
代码:
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int A = 1e6 + 10;
int a[A];
ll solve(ll m,ll x){
ll euler = x;
int tot = 0;
for(ll i=2 ;i*i<=x ;i++){
if(x % i == 0){
euler = euler / i * (i-1);
a[tot++] = i;
while(x%i == 0) x/=i;
}
}
if(x>1){
euler = euler / x * (x-1);
a[tot++] = x;
}
ll res = 0;
for(ll i=1 ;i<(1<<tot) ;i++){
int num = 0;
for(int j=i ;j!=0 ;j>>=1) num += j&1;
ll lcm = 1;
for(int j=0 ;j<tot ;j++){
if((i>>j) & 1){
lcm *= a[j];
if(lcm > m) break;
}
}
if(num & 1) res += m/lcm;
else res -= m/lcm;
}
return m - res - euler;
}
int main(){
//freopen("input","r",stdin);
int T,_=1;
scanf("%d",&T);
while(T--){
ll x,y,c,d,k;
scanf("%I64d%I64d%I64d%I64d%I64d",&x,&y,&c,&d,&k);
if(d < y) swap(d,y);
if(k == 0 || k>y || k>d){
printf("Case %d: 0\n",_++);
continue;
}
ll ans = 0;
for(ll i=k ;i<=y ;i+=k){
ans += solve(d/k,i/k);
}
printf("Case %d: %I64d\n",_++,ans+1);
}
return 0;
}
莫比乌斯反演思路:
参考文档:https://wenku.baidu.com/view/fbec9c63ba1aa8114431d9ac.html?from=search(图片转侵删)
莫比乌斯反演常用的两大公式:
而此题我们需要用到的是第一种形式。
我自己的理解的话莫比乌斯反演类似于矩阵的相似,当一个函数
f
(
x
)
f(x)
f(x)很难根据定义直接求解时,我们可以求出与其具有一定相似关系的另一个函数
F
(
x
)
F(x)
F(x),如倍数和或约数和(其转换关系分别对应于上述两张图)
根据题意,题目可以等价转化为:
已知
x
∈
[
1
,
n
]
和
y
∈
[
1
,
m
]
求
g
c
d
(
x
,
y
)
=
=
1
的
数
量
x \in [1,n] 和 y \in[1,m] 求 gcd(x,y)==1的数量
x∈[1,n]和y∈[1,m]求gcd(x,y)==1的数量
我们假设:
f
(
i
)
f(i)
f(i)为
g
c
d
(
x
,
y
)
=
=
i
gcd(x,y) == i
gcd(x,y)==i的个数
F
(
i
)
F(i)
F(i)为 $ gcd(x,y) == (i的倍数)
的
个
数
。
则
可
推
出
两
个
函
数
满
足
第
一
种
形
式
的
等
价
关
系
,
即
可
得
:
因
为
由
的个数。 则可推出 两个函数满足第一种形式的等价关系,即可得: 因为由
的个数。则可推出两个函数满足第一种形式的等价关系,即可得:因为由F(i)$的定义可以得出:
对于gcd(x,y) == (i的倍数), 则x,y一定也是i的倍数
而在【1,n】中i的倍数有[n/i],【1,m】中i的倍数有[m/i] ([x]表示对x向下取整)
故由排列组合知识 :
F
[
i
]
=
[
n
/
i
]
∗
[
m
/
i
]
F[i] = [n/i] * [m/i]
F[i]=[n/i]∗[m/i]
又由莫比乌斯变换,得出:
这样就可以在O(n)的复杂度下得到答案了。
对于此题还有一个去重的细节:举例
对于区间区间【1,3】 和区间【1,5】
重复的gcd有 (1,1),(1,2),(1,3),(2,3) 与 (1,1),(2,1),(3,1),(3,2)
显然重复都是由于其具有重合的区间【1,3】,所以我们只需要再算一下
x,y分别属于【1,3】和【1,3】区间的答案,减去这一部分答案的1/2即可。
但对于该题的升级版:BZOJ 2301
这样的复杂度是不能解决问题的,因为涉及除法,可以分块并维护莫比乌斯函数的前缀和进行优化,详细讲解见参考文档:
代码:HDU 1695
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e5 + 10;
bool vis[A];
int pri[A],mu[A],tot;
ll sum[A];
void init(){
mu[1] = 1;
vis[0] = vis[1] = 1;
tot = 0;
for(int i=2 ;i<A ;i++){
if(vis[i] == 0){
pri[++tot] = i;
mu[i] = -1;
}
for(int j=1 ;j<=tot && pri[j]*i<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
mu[i*pri[j]] = 0;
break;
}
mu[i*pri[j]] = -mu[i];
}
}
sum[0] = 0;
for(int i=1 ;i<A ;i++){
sum[i] = sum[i-1] + mu[i];
}
}
int main(){
init();
int T,_=1;
scanf("%d",&T);
while(T--){
int a,b,c,d,k;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
if(k == 0){
printf("Case %d: 0\n",_++);
continue;
}
int n = b/k,m = d/k;
if(n>m) swap(n,m);
int last;
ll sum1,sum2;
sum1 = sum2 = 0;
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
sum1 += (sum[last] - sum[i-1])*(n/i)*(m/i);
}
for(int i=1 ;i<=n ;i=last+1){
last = n/(n/i);
sum2 += (sum[last] - sum[i-1])*(n/i)*(n/i);
}
printf("Case %d: %I64d\n",_++,sum1 - sum2/2);
}
return 0;
}
BZOJ 2301:
/**************************************************************
Problem: 2301
User: WuBaizhe
Language: C++
Result: Accepted
Time:10828 ms
Memory:1652 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 5e4 + 10;
bool vis[A];
int pri[A],mu[A],tot,k;
ll sum[A];
void init(){
tot = 0;
vis[0] = vis[1] = 1;
mu[1] = 1;
for(int i=2 ;i<A ;i++){
if(vis[i] == 0){
pri[++tot] = i;
mu[i] = -1;
}
for(int j=1 ;j<=tot&&pri[j]*i<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
mu[i*pri[j]] = 0;
break;
}
mu[i*pri[j]] = -mu[i];
}
}
sum[0] = 0;
for(int i=1 ;i<A ;i++){
sum[i] = sum[i-1] + mu[i];
}
}
ll solve(int x,int y){
int n = x/k,m = y/k;
if(n > m) swap(n,m);
ll sum1 = 0,sum2 = 0;
int last;
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
sum1 += (sum[last] - sum[i-1])*(n/i)*(m/i);
}
return sum1;
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int a,b,c,d;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
//printf("test: %lld\n",solve(b,d));
ll ans = solve(b,d) - solve(a-1,d) - solve(b,c-1) + solve(a-1,c-1);
printf("%lld\n",ans);
}
return 0;
}