1. 程式人生 > >莫比烏斯反演的學習(HDU1695)

莫比烏斯反演的學習(HDU1695)

原文地址

   前兩天學習了一下之前一直覺得高大上並且想學的內容——莫比烏斯反演。不過學任何東西都是一樣,學會了發現也就這樣,雖然只是皮毛。OK,廢話不多說,進入正題,今天我用杭電的1695這道題再來溫習一下莫比烏斯反演。

   HDU1695的題目大意是這樣的,給你 a , b , c , d , k 五個值 (題目說明了 你可以認為 a=c=1)  x 屬於 [1,b] ,y屬於[1,d]  讓你求有多少對這樣的 (x,y)滿足gcd(x,y)==k。給你的時間是 3000 MS。   0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000

   這道題我剛開始看的時候想到的是容斥定理, 由於以前沒用容斥寫過題,但久聞莫比烏斯反演大名,乾脆就學習了一下,如何用莫比烏斯反演來解題。

   首先 ,這道題可以進行一部分的簡化,因為  gcd(x,y)=k  那麼,很顯然 gcd(x / k,y / k)是等於 1 的(x,y 除了 k 一定沒有其他的公因數)。那麼,此時問題就可以轉化為: x 屬於 [1,b / k] ,y屬於[1,d / k]  讓你求有多少對這樣的 (x,y)滿足gcd(x,y)== 1 即x和y是互質的。 走到這一步 ,題目算是解決了一半了,我們先來看一下什麼是莫比烏斯反演。

   這裡先給出莫比烏斯的兩個公式 :  (以下圖片摘自 ACdreamer 的部落格,僅供學習交流使用)

                       

                       

OK 這兩個就是莫比烏斯反演的兩種表現形式 反演的核心所在是莫比烏斯函式,什麼是莫比烏斯函式呢? 我們在下面給出它的定義:

   是的 沒錯 我們稱之為 mu 函式,它就是莫比烏斯函式,也是整個反演的最為重要的部分。

   現在我們來繼續解決上面的那個問題。如何去求有多少對這樣的 (x,y)滿足gcd(x,y)== 1 。這個問題你直接拿到手發現確實比較麻煩,但是換個思路,如果我們去求有多少對這樣的 (x,y)滿足 gcd(x,y)== 1 的倍數  呢?  是不是就非常簡單了呢?   

   OK ! 我們試著來設一下 F(d)為 有多少對(x,y)滿足 gcd(x,y)== d 的倍數 。

 f(d)為有多少對(x,y)滿足 gcd(x,y)== d  。

   呵呵,你發現F(d)用初中數學都能求出是   (n=b / k,m=d / k)

   那麼其實我們需要解決的就剩下如何求出 f(1)是多少的問題了。

   根據公式你可以發現,在你對函式進行題設時是需要滿足反演對函式的要求的,這個需要你自己來體會,至於另一個公式的設法是 “約數” 的關係,而這個則是 “倍數” 的關係。

  那麼問題就基本上解決了,f(1)= mu(1)*F(1)+ mu(2)*F(2)+……  這個式子的終止條件是什麼呢?很顯然在所限定的區間內,d最大為 min(m,n)。

那麼完整的式子就應該是  f(1)= mu(1)*F(1)+ mu(2)*F(2)+……mu(min(m,n))*F(min(m,n))。至此 這道題目就順利的解決了。

下面給出如何求 mu 函式的程式碼 下列程式碼求得了 mu 函式 1-n 的函式值,直接使用就行

第一種 普通篩選求莫比烏斯函式 時間複雜度為  O(nlogn)

  1. void getMu(){
  2. int N=maxn;
  3. for(int i=1;i<N;++i){
  4. int target=i==1?1:0;
  5. int delta=target-mu[i];
  6. mu[i]=delta;
  7. for(int j=2*i;j<N;j+=i)
  8. mu[j]+=delta;
  9. }
  10. }
第二種 線性篩選求莫比烏斯函式 時間複雜度為  O(n)
  1. void Init(){
  2. int N=maxn;
  3. memset(prime,0,sizeof(prime));
  4. memset(mu,0,sizeof(mu));
  5. memset(vis,0,sizeof(vis));
  6. mu[1] = 1;
  7. cnt = 0;
  8. for(int i=2; i<N; i++){
  9. if(!vis[i]){
  10. prime[cnt++] = i;
  11. mu[i] = -1;
  12. }
  13. for(int j=0; j<cnt&&i*prime[j]<N; j++){
  14. vis[i*prime[j]] = 1;
  15. if(i%prime[j]) mu[i*prime[j]] = -mu[i];
  16. else{
  17. mu[i*prime[j]] = 0;
  18. break;
  19. }
  20. }
  21. }
  22. }

此時,走到這一步,我們已經求得了(x,y)滿足 gcd(x,y)=1 的對數 ,但題目中說明了,(1,2)和(2,1)算一種情況,那麼我們就要減去多餘了的情況,怎那麼找出那些多算進去的情況呢? 下面的圖畫的很清楚:

   G(b,b)就是多算進去的這些情況,

   那麼 G(b,d)- G(b,b)/ 2 就是最終我們要求的結果了,至於這一點,有不懂的請在紙上畫一畫,這不是我要講的重點了。

   完整程式碼如下:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int maxn=1e5+7;
  4. bool vis[maxn];
  5. int prime[maxn],mu[maxn];
  6. int cnt;
  7. void Init(){
  8. int N=maxn;
  9. memset(prime,0,sizeof(prime));
  10. memset(mu,0,sizeof(mu));
  11. memset(vis,0,sizeof(vis));
  12. mu[1] = 1;
  13. cnt = 0;
  14. for(int i=2; i<N; i++){
  15. if(!vis[i]){
  16. prime[cnt++] = i;
  17. mu[i] = -1;
  18. }
  19. for(int j=0; j<cnt&&i*prime[j]<N; j++){
  20. vis[i*prime[j]] = 1;
  21. if(i%prime[j]) mu[i*prime[j]] = -mu[i];
  22. else{
  23. mu[i*prime[j]] = 0;
  24. break;
  25. }
  26. }
  27. }
  28. }
  29. void getMu(){
  30. int N=maxn;
  31. for(int i=1;i<N;++i){
  32. int target=i==1?1:0;
  33. int delta=target-mu[i];
  34. mu[i]=delta;
  35. for(int j=2*i;j<N;j+=i)
  36. mu[j]+=delta;
  37. }
  38. }
  39. int main()
  40. {
  41. ios::sync_with_stdio(false);
  42. int a,b,c,d,k;
  43. int T,Case=0;
  44. Init();
  45. cin>>T;
  46. while(T--){
  47. cin>>a>>b>>c>>d>>k;
  48. cout<<"Case "<<++Case<<": ";
  49. if(k==0){
  50. cout<<"0"<<endl;
  51. continue;
  52. }
  53. b/=k,d/=k;
  54. long long ans1=0,ans2=0;
  55. for(int i=1;i<=min(b,d);i++){
  56. ans1+=(long long)mu[i]*(b/i)*(d/i);
  57. }
  58. for(int i=1;i<=min(b,d);i++){
  59. ans2+=(long long)mu[i]*(min(b,d)/i)*(min(b,d)/i);
  60. }
  61. cout<<ans1-ans2/2<<endl;
  62. }
  63. return 0;
  64. }

   至此我們通過HDU1695學習了莫比烏斯反演的入門,數論中還有更多有趣的問題等待我們去探索,不得不感嘆這些數學家們的偉大了。

        </div>
            </div>
        </article>