YY的GCD
-
題目描述:神犇YY虐完數論後給傻×kAc出了一題
給定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)為質數的(x, y)有多少對
kAc這種傻×必然不會了,於是向你來請教……
多組輸入(大概也就一萬組吧……)
-
解決本題需要了解莫比烏斯反演的知識,所以先來普及知識點(最為枯燥的部分,但忍下去)。
莫比烏斯函式
-
:莫比烏斯函式是啥啊?
:(莫比烏斯是一個由容斥係數所構成的函式)。
-
μ(d)的定義:
- 當d=1時,μ(d)=1;
- 當d=\(\Pi_{i=1}^kp_i\) 且\(p_i\) 為互質素數時,μ(d)=\((-1)^k\) 。(說直白點,就是d分解質因數後,沒有冪次大於平方的質因子,此時函式值根據分解的個數決定);
- 只要當d含有任何質因子的冪大於等於2,則函式值為0。
-
莫比烏斯函式的一些其他有趣的性質:
- 對於任意正整數n,\(\sum_{d|n}μ(d)\) =[n=1]。(PS:這一條性質是莫比烏斯反演中最常用的)
- 對於任意正整數n,\(\sum_{d|n} \frac{μ(d)}{d}\) =\(\frac{φ(n)}{n}\) 。(這個性質很奇妙,把尤拉函式與莫比烏斯函式結合起來,下次學杜教篩會證明)。
-
求莫比烏斯函式的程式實現不難,只需要線上性篩的程式上略作修改,便可以篩出μ函式。
void mu_get(){ mu[1]=1; for(int i=2;i<=n;++i){ if(!v[i]){ p[++m]=i,mu[i]=-1; } for(int j=1;j<=m;++j){ if(p[j]>v[i]||i*p[j]>n) break; v[i*p[j]]=p[j]; if(i%p[j]) break; else mu[i*p[j]]=-mu[i]; } } }
莫比烏斯反演
-
解決了莫比烏斯函式的問題後,我們就迎來了噁心的莫比烏斯反演。
-
莫比烏斯反演定理:F(n)和f(n)是定義在非負整數集合上的兩個函式,並且滿足條件:
\[F(n)=\sum_{d|n}f(d)\]
那麼現在存在一個結論:
\[f(n)=\sum_{d|n}μ(d)F(\frac{n}{d})\]
-
莫比烏斯反演的證明主要有兩種方式,其中一種就是通過定義來證明;另一種利用狄利克雷卷積。先來說一說第一種證明方法:
\[\sum_{d|n}μ(d)F(\frac{n}{d})=\sum_{d|n}μ(d)\sum_{i|\frac{n}{d}}f(i)\]
\[=\sum_{i|n}f(i)\sum_{d|\frac{n}{i}}μ(d)=f(n)\]
(PS:如果不知道第二部如何推導第三部,可以考慮第二個式子列舉的i與d的關係,發現可以調換一下列舉的方式。如果不知道最後一步怎麼來的,可以去看一下上面莫比烏斯函式的性質1)。
-
當然,莫比烏斯反演有另外的一種形式,當F(n)和f(n)滿足:
\[F(n)=\sum_{n|d}f(d)\]
可以推出:
\[f(n)=\sum_{n|d}μ(\frac{d}{n})F(d)\]
-
感覺這個式子,可能在莫比烏斯反演中更加好用。
回到本題
-
顯然,Ans=\(\sum_{p\epsilon prim}\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=p]\)
-
對於這種和gcd有關的莫比烏斯反演,我們一般另f(d)為gcd(i,j)=d的個數,即f(d)=\(\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=d]\) 。那麼F(n)為gcd(i,j)為n或者n的倍數的個數,即:
\[F(n)=\sum_{n|d}f(d)=\lfloor\frac{N}{n}\rfloor\lfloor\frac{M}{m}\rfloor\]
那麼根據莫比烏斯反演定理:
\[f(n)=\sum_{n|d}μ(\frac{d}{n})F(d)\]
-
接下來就是化簡式子了!
\[Ans=\sum_{p\epsilon prim}f(p)\]
\[=\sum_{p\epsilon prim}\sum_{p|d}μ(\frac{d}{p})F(d)\]
換一個列舉項,列舉\(\lfloor\frac{d}{p}\rfloor\)
\[\sum_{p\epsilon prim}\sum_{d=1}^{min(\lfloor\frac{n}{p}\rfloor,\lfloor\frac{m}{p}\rfloor)}μ(d)F(dp)=\sum_{p\epsilon prim}\sum_{d=1}^{min(\lfloor\frac{n}{p}\rfloor,\lfloor\frac{m}{p}\rfloor)}μ(d)\lfloor\frac{n}{dp}\rfloor\lfloor\frac{m}{dp}\rfloor\]
這個dp看著很不爽,我們把它換成T
\[Ans=\sum_{T=1}^{min(n,m)}\sum_{t|T,t\epsilon prim}μ(\frac{T}{t})\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\]
\[Ans=\sum_{T=1}^{min(n,m)}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{t|T,t\epsilon prim}μ(\frac{T}{t})\]
-
這樣就已經可以O(n)求了。不過這道題有多組資料,所以直接O(n)求會掛。所以我們利用字首和的思路,打一個整除分塊,就可以O(n)預處理,O(\(\sqrt{n}\) )求每一組資料了。
歷經磨難終於A掉了這道題,哎數學題推式子我總是看著題解一遍一遍推,還是沒有感覺,弱啊……
Coding
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e7+10; int n,m,cnt,p[N],v[N],mu[N];ll sum[N],ans,g[N]; void get_mu(int a){ mu[1]=1; for(int i=2;i<=a;++i){ if(!v[i]){ p[++cnt]=i;mu[i]=-1; } for(int j=1;j<=cnt;++j){ if(p[j]*i>a) break; v[i*p[j]]=p[j]; if(i%p[j]==0) break; else mu[i*p[j]]=-mu[i]; } } for(int i=1;i<=cnt;++i){ for(int j=1;j*p[i]<=a;++j) g[p[i]*j]+=mu[j]; } for(int i=1;i<=a;++i) sum[i]=sum[i-1]+g[i]; } int main(){ get_mu(1e7); int t;scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); ans=0; if(n>m) swap(n,m); for(int l=1,r;l<=n;l=r+1){ r=min((n/(n/l)),m/(m/l)); ans+=(sum[r]-sum[l-1])*(n/l)*(m/l); } printf("%lld\n",ans); } return 0; }