P2522 [HAOI2011]Problem b
题意:
n个询问,在 a ≤ x ≤ b , c ≤ y ≤ d a\le x\le b, c \le y \le d a≤x≤b,c≤y≤d范围内,满足gcd(x,y)=k的数对有多少个。
Solution:
反演过程
法一:
∑ x = a b ∑ y = c d [ g c d ( x , y ) = k ] = ∑ x = a b ∑ y = c d ∑ k ∣ g c d ( x , y ) [ g c d ( x k , y k ) = 1 ] = ∑ x = ⌈ a k ⌉ ⌊ b k ⌋ ∑ y = ⌈ c k ⌉ ⌊ d k ⌋ [ g c d ( x , y ) = 1 ] = ∑ x = ⌈ a k ⌉ ⌊ b k ⌋ ∑ y = ⌈ c k ⌉ ⌊ d k ⌋ ∑ d 1 ∣ g c d ( x , y ) μ ( d 1 ) = ∑ d 1 = 1 m i n ( b , d ) μ ( d 1 ) ∑ x = ⌈ a k ⌉ ⌊ b k ⌋ ∑ y = ⌈ c k ⌉ ⌊ d k ⌋ 1 = ∑ d 1 = 1 m i n ( b , d ) μ ( d 1 ) ( ⌊ b k d 1 ⌋ − ⌈ a k d 1 ⌉ + 1 ) ( ⌊ d k d 1 ⌋ − ⌈ c k d 1 ⌉ + 1 ) \sum_{x=a}^{b}\sum_{y=c}^{d}[gcd(x,y)=k] \\ =\sum_{x=a}^b\sum_{y=c}^{d}\sum_{k|gcd(x,y)}[gcd(\frac{x}{k},\frac{y}{k})=1] \\ =\sum_{x={\lceil\frac{a}{k}}\rceil}^{\lfloor\frac{b}{k}\rfloor}\sum_{y={\lceil\frac{c}{k}}\rceil}^{\lfloor\frac{d}{k}\rfloor}[gcd(x,y)=1] \\ =\sum_{x={\lceil\frac{a}{k}}\rceil}^{\lfloor\frac{b}{k}\rfloor}\sum_{y={\lceil\frac{c}{k}}\rceil}^{\lfloor\frac{d}{k}\rfloor}\sum_{d_1|gcd(x,y)}\mu(d_1) \\ =\sum_{d_1=1}^{min(b,d)}\mu(d_1)\sum_{x={\lceil\frac{a}{k}}\rceil}^{\lfloor\frac{b}{k}\rfloor}\sum_{y={\lceil\frac{c}{k}}\rceil}^{\lfloor\frac{d}{k}\rfloor} 1 \\ =\sum_{d_1=1}^{min(b,d)}\mu(d_1)(\lfloor\frac{b}{kd_1}\rfloor -\lceil\frac{a}{kd_1}\rceil+1)(\lfloor\frac{d}{kd_1}\rfloor -\lceil\frac{c}{kd_1}\rceil+1) x=a∑by=c∑d[gcd(x,y)=k]=x=a∑by=c∑dk∣gcd(x,y)∑[gcd(kx,ky)=1]=x=⌈ka⌉∑⌊kb⌋y=⌈kc⌉∑⌊kd⌋[gcd(x,y)=1]=x=⌈ka⌉∑⌊kb⌋y=⌈kc⌉∑⌊kd⌋d1∣gcd(x,y)∑μ(d1)=d1=1∑min(b,d)μ(d1)x=⌈ka⌉∑⌊kb⌋y=⌈kc⌉∑⌊kd⌋1=d1=1∑min(b,d)μ(d1)(⌊kd1b⌋−⌈kd1a⌉+1)(⌊kd1d⌋−⌈kd1c⌉+1)
法二:
由通用的反演式子 F ( n ) = ∑ n ∣ d f ( d ) , f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n)=\sum_{n|d}f(d),f(n)=\sum_{n|d}\mu(\frac{d}{n})F(d) F(n)=∑n∣df(d),f(n)=∑n∣dμ(nd)F(d)推出
设
f
(
k
)
=
∑
x
=
a
b
∑
y
=
c
d
[
g
c
d
(
x
,
y
)
=
k
]
f(k)=\sum_{x=a}^{b}\sum_{y=c}^{d}[gcd(x,y)=k]
f(k)=∑x=ab∑y=cd[gcd(x,y)=k]
那么
F
(
n
)
=
∑
n
∣
k
f
(
k
)
=
∑
n
∣
k
∑
x
=
a
b
∑
y
=
c
d
[
g
c
d
(
x
,
y
)
=
k
]
=
∑
x
=
a
b
∑
y
=
c
d
[
n
∣
g
c
d
(
x
,
y
)
]
=
(
⌊
b
n
⌋
−
⌈
a
n
⌉
+
1
)
(
⌊
d
n
⌋
−
⌈
c
n
⌉
+
1
)
F(n)=\sum_{n|k}f(k) \\ =\sum_{n|k}\sum_{x=a}^{b}\sum_{y=c}^{d}[gcd(x,y)=k] \\ =\sum_{x=a}^{b}\sum_{y=c}^{d}[n|gcd(x,y)] \\ =(\lfloor\frac{b}{n}\rfloor -\lceil\frac{a}{n}\rceil+1)(\lfloor\frac{d}{n}\rfloor -\lceil\frac{c}{n}\rceil+1)
F(n)=n∣k∑f(k)=n∣k∑x=a∑by=c∑d[gcd(x,y)=k]=x=a∑by=c∑d[n∣gcd(x,y)]=(⌊nb⌋−⌈na⌉+1)(⌊nd⌋−⌈nc⌉+1)
反过来推出(对后面的式子中有个说明n=d1*k)
f
(
k
)
=
∑
k
∣
n
μ
(
n
k
)
F
(
n
)
=
∑
k
∣
n
μ
(
n
k
)
(
⌊
b
n
⌋
−
⌈
a
n
⌉
+
1
)
(
⌊
d
n
⌋
−
⌈
c
n
⌉
+
1
)
=
∑
d
1
=
1
m
i
n
(
b
k
,
d
k
)
μ
(
d
1
)
(
⌊
b
k
d
1
⌋
−
⌈
a
k
d
1
⌉
+
1
)
(
⌊
d
k
d
1
⌋
−
⌈
c
k
d
1
⌉
+
1
)
f(k)=\sum_{k|n}\mu(\frac{n}{k})F(n) \\ =\sum_{k|n}\mu(\frac{n}{k})(\lfloor\frac{b}{n}\rfloor -\lceil\frac{a}{n}\rceil+1)(\lfloor\frac{d}{n}\rfloor -\lceil\frac{c}{n}\rceil+1) \\ =\sum_{d_1=1}^{min(\frac{b}{k},\frac{d}{k})}\mu(d_1)(\lfloor\frac{b}{kd_1}\rfloor -\lceil\frac{a}{kd_1}\rceil+1)(\lfloor\frac{d}{kd_1}\rfloor -\lceil\frac{c}{kd_1}\rceil+1)
f(k)=k∣n∑μ(kn)F(n)=k∣n∑μ(kn)(⌊nb⌋−⌈na⌉+1)(⌊nd⌋−⌈nc⌉+1)=d1=1∑min(kb,kd)μ(d1)(⌊kd1b⌋−⌈kd1a⌉+1)(⌊kd1d⌋−⌈kd1c⌉+1)
到这里,反演的式子也就推出来了。但是,你不会以为到这里就结束了吧。不会吧!不会吧!
当你把代码写出来,然后提交,惊喜的发现WA了(可达鸭眉头一皱,发现事情并不简单)。
若是暴力的去跑这个反演式子,你就会发现复杂度并不够优秀,TLE了,因此要对这个过程进行优化,通过分块优化这个过程。
但是只是套上一个分块,并对
μ
\mu
μ进行前缀求和是不够的。
要发现前缀求和的这个特点
∑
i
=
a
b
∑
j
=
c
d
(
.
.
.
)
=
∑
i
=
1
b
∑
j
=
c
d
(
.
.
.
)
−
∑
i
=
1
a
−
1
∑
j
=
c
d
(
.
.
.
)
=
∑
i
=
1
b
∑
j
=
1
d
(
.
.
.
)
−
∑
i
=
1
b
∑
j
=
1
c
−
1
(
.
.
.
)
−
(
∑
i
=
1
a
−
1
∑
j
=
1
d
(
.
.
.
)
−
∑
i
=
1
a
−
1
∑
j
=
1
c
−
1
(
.
.
.
)
)
=
∑
i
=
1
b
∑
j
=
1
d
(
.
.
.
)
−
∑
i
=
1
b
∑
j
=
1
c
−
1
(
.
.
.
)
−
∑
i
=
1
a
−
1
∑
j
=
1
d
(
.
.
.
)
+
∑
i
=
1
a
−
1
∑
j
=
1
c
−
1
(
.
.
.
)
\sum_{i=a}^{b}\sum_{j=c}^{d}(...) \\ =\sum_{i=1}^{b}\sum_{j=c}^{d}(...)-\sum_{i=1}^{a-1}\sum_{j=c}^{d}(...) \\=\sum_{i=1}^{b}\sum_{j=1}^{d}(...)-\sum_{i=1}^{b}\sum_{j=1}^{c-1}(...)-(\sum_{i=1}^{a-1}\sum_{j=1}^{d}(...)-\sum_{i=1}^{a-1}\sum_{j=1}^{c-1}(...)) \\ =\sum_{i=1}^{b}\sum_{j=1}^{d}(...)-\sum_{i=1}^{b}\sum_{j=1}^{c-1}(...)-\sum_{i=1}^{a-1}\sum_{j=1}^{d}(...)+\sum_{i=1}^{a-1}\sum_{j=1}^{c-1}(...)
i=a∑bj=c∑d(...)=i=1∑bj=c∑d(...)−i=1∑a−1j=c∑d(...)=i=1∑bj=1∑d(...)−i=1∑bj=1∑c−1(...)−(i=1∑a−1j=1∑d(...)−i=1∑a−1j=1∑c−1(...))=i=1∑bj=1∑d(...)−i=1∑bj=1∑c−1(...)−i=1∑a−1j=1∑d(...)+i=1∑a−1j=1∑c−1(...)
以上推演过程过于复杂,容易出错,所以一般将[a,b]的范围变成[1,b]-[1,a-1],这样就不用弄清楚上下界取整的情况。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
int mu[50005];
int vis[50005];
int prime[50005],cnt=0;
int pre[50005];
void initMul()
{
mu[1]=1;
for(int i=2;i<=50000;i++)
{
if(!vis[i])
{
mu[i]=-1;
prime[cnt++]=i;
}
for(int j=0;j<cnt&&1ll*i*prime[j]<=50000;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<=50000;i++)pre[i]=pre[i-1]+mu[i];
}
ll calc(int a,int b)
{
ll res=0;
int M=min(a,b);
for(int l=1,r;l<=M;l=r+1)
{
r=min(a/(a/l),b/(b/l));
res+=1ll*(pre[r]-pre[l-1])*(a/l)*(b/l);
}
return res;
}
int main()
{
initMul();
int n;scanf("%d",&n);
while(n--)
{
int a,b,c,d,k;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
ll res=calc(b/k,d/k)-calc(b/k,(c-1)/k)-calc((a-1)/k,d/k)+calc((a-1)/k,(c-1)/k);
printf("%lld\n",res);
}
return 0;
}