1. 程式人生 > >莫比烏斯反演學習記錄(最菜的垃圾而淺薄基礎的總結)

莫比烏斯反演學習記錄(最菜的垃圾而淺薄基礎的總結)

turn 找到 mem += 分解質 並不會 但是 printf 取值

鑒於容易忘,決定先把目前會的寫出來.....

莫比烏斯函數:
對於一個整數N,按照算數基本定理分解質因數為N = p1^c1 * p2^c2 * p3^c3 * ... * pm^cm
0 存在i∈[1, m],ci>1
μ(N) = { 1 m≡0(mod 2),任意i∈[1, m],ci = 1
-1 m≡1(mod 2),任意i∈[1, m],ci = 1
通俗講:
①當N包含相等的質因子時,μ(N) = 0
②當N的所有質因子各不相等時,若N有偶數個質因子,μ(N) = 1
③當N有奇數個質因子時,μ(N) = -1
當然百度的話顯然能找到更好的說明方式,但是我覺得這樣比較清楚(雖然不利於理解求莫比烏斯函數)

莫比烏斯函數求法

 1 memset(vis, 0, sizeof(vis));
 2     miu[1] = 1, vis[1] = 1;
 3     for(int i = 2; i < v; ++i) {
 4         if(!vis[i]) prime[++cnt] = i, miu[i] = -1;
 5         for(int j = 1; j <= cnt; ++j) {
 6             if(prime[j] > v / i) break;
 7             vis[i * prime[j]] = 1;
 8             if
(i % prime[j] == 0) {//說明對於i * prime[j], 其中prime[j]的冪大於1 9 miu[prime[j] * i] = 0; 10 break; 11 } 12 miu[prime[j] * i] = -miu[i]; 13 } 14 }

莫比烏斯反演:
<1>莫比烏斯函數的一些性質:
·對於任意正整數n,Σμ(d) = [n=1] (d|n)
  證明:當n = 1時,顯然有函數g(n) = Σμ(d) = 1 (d|n)


     當n = p^k時(p為質數)
      g(n) = Σμ(d) = μ(1)+μ(p)+μ(p^2)+...+μ(p^k)
            = 1 + (-1) + 0 + ... + 0 = 0
     當n = p1^c1 * p2^c2 * ... * pk^ck時
      g(n) = g(p1^c1)g(p2^c2)....g(pk^ck) = 0
·對於任意正整數n,Σμ(d)/d = φ(n)/n (d|n) //然而我並不會證明

<2>莫比烏斯反演
定理:F(n), f(n)為兩個非負整數集合上的函數
   F(n) = Σf(d) (d|n) <==> f(n) = ΣΣμ(d)*f(e) (前一個Σ範圍為d|n,後一個Σ範圍為e|(n/d))
   證明:f(n)=Σμ(d)F(n/d) = Σμ(d)(d|n) * Σf(e)(e|(n/d)) = Σ(d|n)Σ(e|n/d)μ(d)*f(e)
交換求和順序有:f(n) = Σf(e)(e|n)Σμ(d)(d|n/e)
根據莫比烏斯函數性質1,當且僅當n/e == 1,也就是n = e時,Σμ(d) = 1(d|n/e)
      則f(n) = f(e)*1 = f(n)
   證畢

(關於莫比烏斯反演,還有狄利克雷卷積證法,但是我不會.jpg)
莫比烏斯反演公式圖片搬運版(因為我根本不會markdown語法)

技術分享圖片

大佬眼中的入門題:
Bzoj2301

技術分享圖片

以cal(a, b, k)表示對於x<=a, y<=b,gcd(x, y)=k的x, y的對數
則我們求的是
ΣΣ[gcd(i,j)==k](1<=i<=n, 1<=j<=m)
則根據容斥原理,答案顯然為
cal(b, d, k) - cal(a - 1, d, k) - cal(b, c - 1, k) + cal(a - 1, b - 1, k)
/*
對於問題ΣΣ[gcd(i,j)==k](1<=i<=n, 1<=j<=m),可以轉化為
求對於1<=x<=[n/k],1<=y<=[m/k],gcd(x,y)互質的對數,也就是
ΣΣ[gcd(i,j)==1](1<=i<=[n/k], 1<=j<=[m/k])
實際上莫比烏斯反演式的本質就是Σμ(d)=[n==1] (d|n)
因此有Σμ(d)=[gcd(i,j)==1] (d|gcd(i,j))
因此也就是求
ΣΣΣμ(d) (1<=i<=[n/k], 1<=j<=[m/k], d|gcd(i,j))
.....
這些是另一個常見推法的基本操作,但是我不會和式變換所以我不會這麽推....
*/
f(i)表示1<=x<=n, 1<=y<=m, 且gcd(x,y)=i的數對(x,y)的個數
F(i)表示1<=x<=n, 1<=y<=m, 且i|gcd(x,y)的數對(x,y)的個數
則F(i)=[n/i]*[m/i],
F[i]=Σf(d)(d|i)
→f(i)=Σμ(d/i)F(d) (i|d)
→f(i)=Σμ(d/i)[n/d][m/d] (i|d)
對於題目,我們只需要求f(k),然後枚舉d(d為k的倍數)
也就是枚舉k的每一個倍數可以在O(n)復雜度內回答一次詢問,顯然不能通過題目
考慮優化,容易註意到:我們對於k的倍數的枚舉取決於[n/d]和[m/d]的取值
首先分析[n/d]的取值
1)當1<=d<=[sqrt(n)]時,最多有[sqrt(n)]種不同的取值
2)當[sqrt(n)]+1<=d<=n是,由於[n/d]<=[sqrt(n)],因此有[sqrt(n)]種不同的取值
綜上所述+同理:
[n/d]有2[sqrt(n)]種不同的取值
[m/d]有2[sqrt(m)]種不同的取值
那麽[n/d][m/d]有多少種不同的取值呢
首先,不是4[sqrt(n)][sqrt(m)]個
考慮在一個數軸上,[n/d]將一段變成了2[sqrt(n)]個塊,每個塊內取值相同,表示有2[sqrt(n)]中取值
同理考慮[m/d],然後手畫一個圖,可以發現,對於[n/d]劃出的塊和[m/d]劃出的塊的間斷點是不同的
將[n/d]和[m/d]合並,其實是間斷點的合並,即:塊的數量變為了2[sqrt(n)]+2[sqrt(m)]
也就是說[n/d][m/d]一共有2[sqrt(n)]+2[sqrt(m)]個取值
對莫比烏斯函數維護前綴和,對於每一段就可以直接回答了
令n <= m這樣對於每次詢問,復雜度為O(sqrt(n))
總復雜度就是O(q*sqrt(n))

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define uint unsigned int
 4 #define ull unsigned long long
 5 using namespace std;
 6 const int maxn = 50010;
 7 int miu[maxn], sum_miu[maxn];
 8 int T, a, b, c, d, k, cnt = 0;
 9 int prime[maxn], vis[maxn];
10 
11 inline int read() {
12     int x = 0, y = 1;
13     char ch = getchar();
14     while(!isdigit(ch)) {
15         if(ch == -) y = -1;
16         ch = getchar();
17     }
18     while(isdigit(ch)) {
19         x = (x << 1) + (x << 3) + ch - 0;
20         ch = getchar();
21     }
22     return x * y;
23 }
24 
25 inline void pre_miu(int v) {
26     memset(vis, 0, sizeof(vis));
27     miu[1] = 1, vis[1] = 1;
28     for(int i = 2; i < v; ++i) {
29         if(!vis[i]) prime[++cnt] = i, miu[i] = -1;
30         for(int j = 1; j <= cnt; ++j) {
31             if(prime[j] > v / i) break;
32             vis[i * prime[j]] = 1;
33             if(i % prime[j] == 0) {//說明對於i * prime[j], 其中prime[j]的冪大於1 
34                 miu[prime[j] * i] = 0;
35                 break;
36             }
37             miu[prime[j] * i] = -miu[i];
38         }
39     }
40     for(int i = 2; i < v; ++i) miu[i] += miu[i - 1];
41 }
42 
43 inline ll calc(int n, int m, int k) {//枚舉不同的值,last指向下一個間斷點,這個方法又被稱為數論分塊 
44     n /= k, m /= k;
45     if(n > m) swap(n, m);
46     int last = 0; ll res = 0;
47     for(int i = 1; i <= n; i = last + 1) {
48         last = min(n / (n / i), m / (m / i));
49         res += 1LL * (n / i) * (m / i) * (miu[last] - miu[i - 1]);
50     }
51     return res;
52 }
53 
54 int main() {
55     T = read(); 
56     pre_miu(maxn);
57     while(T--) {
58         a = read(), b = read(), c = read(), d = read(), k = read();
59         ll ans = calc(b, d, k) - calc(a - 1, d, k) - calc(b, c - 1, k) + calc(a - 1, c - 1, k);
60         printf("%lld\n", ans);
61     }
62     return 0;
63 }

後續的題目回頭慢慢補咕咕咕....

莫比烏斯反演學習記錄(最菜的垃圾而淺薄基礎的總結)