莫比乌斯反演

 前言:

   理论部分没写完+很不严谨……民那桑将就着看吧……以后有空补充……

  数论部分详细参考任何一本数论教材。

  同理,组合数学部分详细参考任何一本组合数学教材。



mobius函数 μ(x) 定义为

            = (-1) 【n = p1×p2×p3... ×pr,其中pi为不同的素数】

  μ(x)   = 1   【n = 1】

       = 0   【其他,比方说 n = 4 = 22

 



 

积性函数:

  对于任意互素的正整数a,b,均有f(ab) = f(a) f(b), 则f为积性函数。

如:φ(x) 欧拉函数,μ(x) mobius函数。

 

完全积性:

  对于任意正整数a,b,均有f(ab) = f(a) f(b), 则f为完全积性函数。

如:Legendre符号——

(此处为本人不严谨地说明…… )



重要结论:

1. Σd|nφ(d)=n

2. Σd|nμ(d) = 1 (n == 1)

 [Σd|nμ(d) = 0 (n > 1)]



 

mobius反演公式:

F(n) = Σd|nf(d) ↔ f(n) = Σd|nμ(n/d)F(d) 【注:这里的F,f为数论函数……不是随便什么函数都可以的啊…… 不过一般在竞赛中用到的貌似就是欧拉函数+莫比乌斯函数……】



碎碎念:

其实mobius函数-1,1什么的就是容斥里面+x1 -x2 ...的那个系数,当然我的意思是如果和容斥挂钩的话——因为我本人一开始草草一看就是这么理解的……所以偏好把mobius看成容斥——封装得很好的容斥。

当然,其实完全不必把mobius看成是容斥的一部分——这样感觉好像它只是容斥的一个特例的样子。

但是,就算是作为数论函数来看,mobius函数也是很重要的,意义远不止是容斥原理。

比方说积性啊什么的。

但是有些acm题如果用mobius函数的话就比较不用想容斥里面+-什么的了。比方说求1≤a≤m && 1≤b≤n &&gcd(a,b) == 1的a,b的组合数的这种类型的题目。

 



据说mobius在acm竞赛中常用的写代码技巧是:线性筛选+分块

详细参考——"贾志鹏线性筛"。

先奉上普通的方法(就是根据定义写的):

 1 int mobius(int n) {
 2   int miu = 0;
 3   if(n == 1) return 1;
 4   for(int i = 2; i < sqrt(n); i++) {
 5     if(n % i == 0) {
 6       m *= -1;
 7       int flag = 0;
 8       while(n % i == 0) {
 9         n /= i;
10         flag = 1;
11       }
12       if(flag)return 0;
13     }
14   }
15   if(n > 1) return m *= -1;
16   return m;
17 }
NOM_Mobius

 

然后是用筛选法做的(自己根据定义写的……暂时没发现bug,而且还用此过了HDU 1695)

 1 const int maxn = 100011;
 2 int miu[maxn];
 3 int prime[maxn];
 4 void mobius() {
 5     memset(prime,0,sizeof prime);
 6     miu[1] = 1;
 7     prime[1] = 0;
 8     for(int i = 2; i<maxn; i++) {
 9         if(prime[i] == 0) {
10             long long j = (long long )i*(long long )i;
11             for(; j<maxn; j+=i) {
12                 if(prime[j] == 0)
13                     prime[j] = i;
14             }
15             miu[i] = -1;
16             continue;
17         }
18 
19         int n = i;
20         miu[i] = 1;
21         while(prime[n]!=0) {
22             int tp = prime[n];
23             int flag = 0;
24             while(n % tp == 0) {
25                 flag++;
26                 if(flag==2) {
27                     miu[i] = 0;
28                     break;
29                 }
30                 n /= tp;
31             }
32             miu[i] *= -1;
33             if(flag == 2 ) break;
34         }
35         if(n>1)miu[i] *= -1;
36     }
37 }
Sieve_Mobius

 

 

例子:



HDU 1695

题意:已知a,b,c,d,k,求有多少x,y满足a≤x≤b, c≤y≤d,使得gcd(x,y) = k。

  然后本题a = c = 1.

  公式计算部分参考"贾"论文…… [电脑上打数学公式简直作死……]

1. 可以用容斥+欧拉函数

 

2. 可以用莫比乌斯(mobius)反演

 1 #include<iostream>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<algorithm>
 6 #include<cmath>
 7 using namespace std;
 8 const int maxn = 100011;
 9 int miu[maxn];
10 int prime[maxn];
11 void mobius() {
12   memset(prime,0,sizeof prime);
13   miu[1] = 1;
14   prime[1] = 0;
15   for(int i = 2; i<maxn; i++) {
16     if(prime[i] == 0) {
17       long long j = (long long )i*(long long )i;
18       for(; j<maxn; j+=i) {
19         if(prime[j] == 0)
20           prime[j] = i;
21       }
22       miu[i] = -1;//cout<<"miu["<<i<<"]: "<<miu[i]<<endl;
23       continue;
24     }
25 
26     int n = i;
27     miu[i] = 1;
28     //if(i == 30)cout<<"i:"<<i<<" prime["<<n<<"]: "<<prime[n]<<endl;
29     while(prime[n]!=0) {
30 //            if(i == 30)
31 //                    cout<<"n:"<<n<<endl;
32       int tp = prime[n];
33       int flag = 0;
34       while(n % tp == 0) {
35         flag++;
36         if(flag==2) {
37           miu[i] = 0;
38           break;
39         }
40         n /= tp;
41       }
42 //            if(i == 30)
43 //                    cout<<"n:"<<n<<"flag:"<<flag<<endl;
44       miu[i] *= -1;
45       if(flag == 2 ) break;
46     }
47     if(n>1)miu[i] *= -1;
48     //cout<<"miu["<<i<<"]: "<<miu[i]<<endl;
49   }
50 }
51 int main() {
52 #ifdef LOCALL
53   //freopen("in","r",stdin);
54   //freopen("out","w",stdout);
55 #endif
56   int a,b,c,d,k;
57   mobius();
58   //cout<<miu[100000];
59   int t;
60 //    while(cin>>t)
61 //    cout<<"miu["<<t<<"]:"<<miu[t]<<endl;
62   cin>>t;
63   for(int kase = 1; kase < t+1; kase ++) {
64     cin>>a>>b>>c>>d>>k;
65     if(k == 0) {
66       cout<<"Case "<<kase<<": "<<"0
";
67       continue;
68     }
69     long long ans = 0,repeat = 0;
70     b /= k;
71     d /= k;
72     int a1 = b<d?b:d;
73     for(int i = 1; i<=a1; i++) {
74       ans += (long long)miu[i]*(b/i)*(d/i);
75     }
76     for(int i = 1; i<=a1; i++) {
77       repeat += (long long)miu[i]*(a1/i)*(a1/i);
78     }
79 
80     cout<<"Case "<<kase<<": "<<ans-repeat/2<<"
";
81   }
82   return 0;
83 }
HDU 1695

BZOJ 2301 

题意:题意:已知a,b,c,d,k,求有多少x,y满足a≤x≤b, c≤y≤d,使得gcd(x,y) = k。

100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000

和上一题比起来就是a,c不一定等于1。。。

首先,如果我们按照上题那样做,在思路上完全没有问题,然后BZOJ这题给了50s,其实我觉得怎么想都是想给这种方法一个活路……

          公式为  answer = [1,b; 1, d] - [1,a-1; 1, d] - [1,b; 1, c-1] + [1,a-1; 1,c-1]  

    【[a,b;c,d]的意思为 x∈[a,b] && y∈[c,d]的gcd满足条件的组合数

分析一下复杂度: 1. 筛选法初始化mobius 大概O(nlog(n)log(log(n)))……额 这是Eratosthenes筛选法的复杂度…… 

         2. 最坏可能是 求x,y都属于[1 - n]的可能组合数 那就是 O(n)

         3. 可能有多个测试数据…… 然后这个数据个数也有n这么多……

        话说 n 是 50000啊!

    也就是这样算,去除零头什么的,大概是 O(n*n) = O(25×108) ——  106≈ 1s, 2500*106 ≈ 2500s……

……这…… 果断活不成……  

但是我还是试了试…… 果断超时

贴个超时代码~

 1 #include<iostream>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<algorithm>
 6 #include<cmath>
 7 using namespace std;
 8 typedef long long LL;
 9 const int maxn = 100011;
10 int p,nn,plen;
11 int miu[maxn];
12 int pri[maxn];
13 int prime[maxn];
14 
15 void mobius() {
16   memset(pri,0,sizeof pri);
17   memset(prime,0,sizeof prime);
18 
19   miu[1] = 1;
20   pri[1] = 0;
21   plen = 0;
22   for(int i = 2; i<maxn; i++) {
23     if(pri[i] == 0) {
24       prime[plen++] = i;
25       LL j = (LL )i*(LL )i;
26       for(; j<maxn; j+=i) {
27         if(pri[j] == 0)
28           pri[j] = i;
29       }
30       miu[i] = -1;
31       continue;
32     }
33 
34     int n = i;
35     miu[i] = 1;
36     while(pri[n]!=0) {
37       int tp = pri[n];
38       int flag = 0;
39       while(n % tp == 0) {
40         flag++;
41         if(flag==2) {
42           miu[i] = 0;
43           break;
44         }
45         n /= tp;
46       }
47       miu[i] *= -1;
48       if(flag == 2 ) break;
49     }
50     if(n>1)miu[i] *= -1;
51   }
52 }
53 
54 int main() {
55 #ifdef LOCALL
56   freopen("in","r",stdin);
57   //freopen("out","w",stdout);
58 #endif
59 
60   mobius();
61   //cout<<miu[100000];
62   int t;
63 
64   scanf("%d",&t);
65   int a,b,c,d,k;
66   while(t--) {
67     scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
68     LL ans = 0, rep = 0;
69     int a1 = b/k<d/k?b/k:d/k;
70     for(int i = 1; i <= a1; i ++) {
71       ans += (long long)miu[i]*(b/k/i)*(d/k/i);
72     }
73     a1 = a/k<c/k?a/k:c/k;
74     for(int i = 1; i <= a1;i++){
75       ans += (long long)miu[i]*((a-1)/k/i)*((c-1)/k/i);
76     }
77 
78     a1 = (a-1)/k<d/k?(a-1)/k:d/k;
79 
80     for(int i = 1; i <= a1; i++) {
81       rep += (long long)miu[i]*((a-1)/k/i)*(d/k/i);
82     }
83     a1 = (c-1)/k<b/k?(c-1)/k:b/k;
84     for(int i = 1; i <= a1;i++){
85       rep += (long long)miu[i]*((c-1)/k/i)*(b/k/i);
86     }
87     printf("%lld
",(ans - rep));
88   }
89   return 0;
90 }
超时啦~

然后就要用到  此报告中的分块优化了……

详细参考POI XIV Stage.1 Queries Zap 解题报告:http://wenku.baidu.com/view/fbe263d384254b35eefd34eb.html

  分块优化的思想说起来高大上,其实是这样的:

  1. 注意平常的mobius,在进行反演之后主要还是进行枚举:

    如求x∈[1,a], y∈[1,b]的gcd(x,y) = 1的全部组合数 (假设 a>b)

1 for(int i = 1; i<=b; i++) {
2        ans += (long long)miu[i]*(a/i)*(b/i);
3 }

  我们需要枚举全部小于b的值 —— 用容斥的说法就是,需要枚举全部gcd(x,y)大于1小于d的可能组合。

  

  2. 我们来看一个图:

  

  当gcd(x,y) = d (d > 1)时,我们可以看到,当gcd(x,y) 落在 [d, new_d]这段里时,a/gcd(x,y) 都等于 a/d。

  就是凭借这个,我们可以进行分块优化。

  因为 i = d 到 i = new_d 的这段都对应于(a/i)*(b/i),所以不用一个个求了,直接可以提取出"(a/i)*(b/i)"公因式,这样的话,只需要把μ(d)到μ(new_d)的值累加就好了,

因为需要μ的累加值,一开始我们也求出来[1,n]的μ值了,所以用前缀和的方法把所有的μ值的累加和先存起来……

  嘛…… 这样优化了以后,算算复杂度:(具体复杂度推导过程,参看那篇POI解题报告+自己推算)

         O(n*(2√(n/k) + 2√(n/k)) + n * √n) ≈ O(250000* √50000) ≈ O(250000* √50000) ≈ O(55*106) ≈ 50s

   50s也就刚好差不了…… 剧透一下:最后跑出来也就11+s而已…… 一开始还以为哪写差了,怎么这么久……(本人代码能力差……)然后随便搜了一个代码复制上去……居然也有10+s ……心理平衡了……

  AC代码……

 1 #include<iostream>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<algorithm>
 6 #include<cmath>
 7 #define minum(a,b) (a)>(b)?(b):(a)
 8 using namespace std;
 9 typedef long long LL;
10 const int maxn = 50011;
11 int miu[maxn];
12 int pri[maxn];
13 int p[maxn];
14 
15 void mobius() {
16   memset(pri,0,sizeof pri);
17 
18   miu[1] = 1;
19   pri[1] = 0;
20   for(int i = 2; i<maxn; i++) {
21     if(pri[i] == 0) {
22       LL j = (LL )i*(LL )i;
23       for(; j<maxn; j+=i) {
24         if(pri[j] == 0)
25           pri[j] = i;
26       }
27       miu[i] = -1;
28       continue;
29     }
30 
31     int n = i;
32     miu[i] = 1;
33     while(pri[n]!=0) {
34       int tp = pri[n];
35       int flag = 0;
36       while(n % tp == 0) {
37         flag++;
38         if(flag==2) {
39           miu[i] = 0;
40           break;
41         }
42         n /= tp;
43       }
44       miu[i] *= -1;
45       if(flag == 2 ) break;
46     }
47     if(n>1)miu[i] *= -1;
48   }
49 }
50 LL sum(int a,int b) {
51   int a1 = minum(a,b);
52   LL ans = 0;
53   int new_i;
54   for(int i = 1,new_i = 0; i <= a1; i = new_i + 1) {
55     new_i = minum(a/(a/i),(b/(b/i)));
56     ans += (LL)(p[new_i] - p[i-1])*(a/i)*(b/i);
57   }
58   return ans;
59 }
60 int main() {
61 #ifdef LOCALL
62   freopen("in","r",stdin);
63   //freopen("out","w",stdout);
64 #endif
65   mobius();
66   p[0] = 0;
67   for(int i = 1; i<maxn; i++) p[i] = p[i-1] + miu[i];
68   int t;
69   scanf("%d",&t);
70   int a,b,c,d,k;
71   while(t--) {
72     scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
73     printf("%lld
",sum(b/k,d/k) - sum((a-1)/k,d/k) - sum(b/k,(c-1)/k) + sum((a-1)/k,(c-1)/k));
74   }
75   return 0;
76 }
BZOJ2301

  

(BTW, BZOJ 的 C++交cin | cout 会 RE …… )



还有啊……mobius反演可以用于求可重复的圆周排……

不过……可能由于我代码能力的问题…… 用mobius写就总是超时……待我写过了再来补充吧……呵呵呵呵呵……

还有啊…… 写这么多……好累啊……

原文地址:https://www.cnblogs.com/PeanutPrince/p/3619694.html