莫(meng)比(bi)乌斯反演--BZOJ2301: [HAOI2011]Problem b
n<=50000个询问,每次问a<=x<=b,c<=y<=d中有多少gcd(x,y)=K的(x,y)。a,b,c,d,K<=50000。
这大概是入门题辣。。这里记一波笔记
当难以计算f(i)而易于计算他的反演式g(i)时,可以通过计算g(i)->反演得到f(i)。
先放莫比乌斯函数的性质:$\sum_{d|i} \mu(d)=\left\{\begin{matrix} 1,i=1\\0,i>1\end{matrix}\right.$,$\sum_{d|i}(\mu(d)*n/d)=\varphi(i)$。
反演式一:$g(i)=\sum_{d|i} f(i) ------> f(i)=\sum_{d|i} \mu(d)g(i/d)$。
证明:$\sum_{d|i} \mu(d)g(i/d)=\sum_{d|i} \mu(d) \sum_{d_1|(i/d)} f(d_1)=\sum_{d|i} \sum_{d_1|(i/d)} \mu(d) f(d_1)$。
注意到$dd_1j=i$,所以对每一个$d=d_2$,$d_1=d_3$都有一个$d=d_3$,$d_1=d_2$与之对应。
所以$上式=\sum_{d|i} \sum{d_1|(i/d)} f(d) \mu(d_1)=\sum_{d|i} f(d) \sum{d_1|(i/d)} \mu(d_1)$,由莫比乌斯函数性质得$=f(i)$。
反演式二:$g(i)=\sum_{i|d} f(i) ------> f(i)=\sum_{i|d} \mu(d/i)g(d)$。
证明同上,略。
这题先容斥,变成问1<=x<=n,1<=y<=m中有多少(x,y)=K,由于(x,y)=K的充要条件是(x/K,y/K)=1,所以变成1<=x<=n/K,1<=y<=m/K中有多少(x,y)=1。
为什么这么变呢,因为假如题目求的是f(i),表示1<=x<=n,1<=y<=m中有多少(x,y)=i,那反演式g(i)表示1<=x<=n,1<=y<=m中有多少i|(x,y),g(i)和f(i)满足反演式2。
而明显的,$g(i)=(n/i)*(m/i)$,这里/是向下取整,所以$f(i)=\sum_{i|d} \mu(d/i)g(d)=\sum_{i|d} \mu(d/i)(n/d)(m/d)$。
这时可以发现(n/d)和(m/d)分别只有$2\sqrt(n)$和$2\sqrt(m)$种取值,把他俩分别叫a和b,而随着d增大a和b会缓慢增大,可能a增大b不变,b增大a不变,也可能a,b都增大,都不变。那么数对((n/d),(m/d))最多只有$2\sqrt(n)+2\sqrt(m)$个,因此(n/d)*(m/d)最多只有$2\sqrt(n)+2\sqrt(m)$种取值。
如果上面的i=1,那么只需要枚举这$2\sqrt(n)+2\sqrt(m)$个(n/d)*(m/d)的值就可以在根号时间内算出答案,因为一个(n/d)*(m/d)的值对应一段连续的d,如果i=1,就可以把连续一段莫比乌斯函数以前缀和来O(1)求和。
入门题。
1 #include<cstring> 2 #include<cstdlib> 3 #include<cstdio> 4 //#include<assert.h> 5 #include<algorithm> 6 //#include<iostream> 7 using namespace std; 8 9 int a,b,c,d,K,T; 10 11 #define maxn 50011 12 int miu[maxn],prime[maxn],lp=0,summiu[maxn]; bool notprime[maxn]; 13 void pre(int n=50001) 14 { 15 miu[1]=summiu[1]=1; 16 for (int i=2;i<=n;i++) 17 { 18 if (!notprime[i]) {prime[++lp]=i; miu[i]=-1;} 19 summiu[i]=summiu[i-1]+miu[i]; 20 for (int j=1;j<=lp && 1ll*i*prime[j]<=n;j++) 21 { 22 notprime[i*prime[j]]=1; 23 if (i%prime[j]) miu[i*prime[j]]=-miu[i]; 24 else {miu[i*prime[j]]=0; break;} 25 } 26 } 27 } 28 29 #define LL long long 30 LL solve(int p,int q) 31 { 32 LL ans=0; 33 for (int i=1,last,to=min(p,q);i<=to;i=last+1) 34 { 35 last=min(p/(p/i),q/(q/i)); 36 ans+=1ll*(p/i)*(q/i)*(summiu[last]-summiu[i-1]); 37 } 38 return ans; 39 } 40 41 int main() 42 { 43 pre(); 44 scanf("%d",&T); 45 while (T--) 46 { 47 scanf("%d%d%d%d%d",&a,&b,&c,&d,&K); 48 printf("%lld\n",solve(b/K,d/K)-solve((a-1)/K,d/K)-solve(b/K,(c-1)/K)+solve((a-1)/K,(c-1)/K)); 49 } 50 return 0; 51 }