P2522 [HAOI2011]Problem b (莫比乌斯反演)
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;
}