莫比乌斯反演的学习

最近来学了一波经常遇到但一直没学的莫比乌斯函数。

其实莫比乌斯函数就是这个东西:

 

 

 

F(n) 推出 f(n) 就是反演。

求莫比乌斯:

 1 void init(){
 2     mu[1] = 1;
 3     for(int i = 2;i<=100000;++i){
 4         if(!vis[i]){
 5             prim.push_back(i);
 6             mu[i] = -1;
 7         }
 8         for(int j = 0;j<prim.size() && i * prim[j] <= 100000;++j){
 9             vis[ i * prim[j] ] = 1;
10             if(i%prim[j]) mu[ i * prim[j] ] = -mu[i];
11             else{
12                 mu[ i * prim[j] ] = 0;
13                 break;
14             }
15         }
16     }
17 }
View Code

 

刷题:

hdu1695

求 [1...n] 区间 和 [1..m]区间中,gcd == 1 的二元组个数。把f(n)记为gcd == n 的二元组个数, F(n)记为 n | gcd 的二元组个数, 很容易知道 F(i) = n/i * m/i。反演求得f(1)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N = 1e5+9;
 5 bool vis[N];
 6 vector<int> prim;
 7 int mu[N];
 8 void init(){
 9     mu[1] = 1;
10     for(int i = 2;i<=100000;++i){
11         if(!vis[i]){
12             prim.push_back(i);
13             mu[i] = -1;
14         }
15         for(int j = 0;j<prim.size() && i * prim[j] <= 100000;++j){
16             vis[ i * prim[j] ] = 1;
17             if(i%prim[j]) mu[ i * prim[j] ] = -mu[i];
18             else{
19                 mu[ i * prim[j] ] = 0;
20                 break;
21             }
22         }
23     }
24 }
25 int main(){
26     init();
27     int T; scanf("%d",&T);
28     for(int cas = 1;cas<=T;++cas){
29         int a,b,c,d,k; scanf("%d %d %d %d %d",&a,&b,&c,&d,&k);
30         if(k==0){
31             printf("Case %d: 0\n",cas);
32             continue;
33         }
34         b/=k; d/=k;
35         ll ans1 = 0,ans2 = 0;
36         if(b>d) swap(b,d);
37         for(int i = 1;i<=min(b,d);++i){
38             ans1 += 1ll * mu[i] * (b/i) * (d / i);
39         }
40         for(int i = 1;i<=b;++i){
41             ans2 += 1ll * mu[i] * (b/i) * (b / i);
42         }
43         printf("Case %d: %lld\n",cas,ans1 - ans2/2);
44     }
45     return 0;
46 }
View Code

 

bzoj2301

求 区间[a..b] 和区间[c..d]中 gcd == k 的二元组个数,就是上面那一题,加个容斥,然后注意到 (n/i) * (m/i) 这个东西会有连续的i是相同的, 所以 i 直接 跳到 n/(n/i) 这个位置,不然会tle

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N = 5e4+9;
 5 bool vis[N];
 6 vector<int> prim;
 7 int mu[N];
 8 int sum[N];
 9 void init(){
10     mu[1] = 1;
11     for(int i = 2;i<=N-8;++i){
12         if(!vis[i]){
13             prim.push_back(i);
14             mu[i] = -1;
15         }
16         for(int j = 0;j<prim.size() && i * prim[j] <= N-8;++j){
17             vis[ i * prim[j] ] = 1;
18             if(i%prim[j]) mu[ i * prim[j] ] = -mu[i];
19             else{
20                 mu[ i * prim[j] ] = 0;
21                 break;
22             }
23         }
24     }
25     for(int i=  1;i<=N-8;++i) sum[i] = sum[i-1] + mu[i];
26 }
27 ll solve(int n,int m){
28     ll ans = 0;
29     if(n>m) swap(n,m);
30     for(int i = 1;i<=min(n,m);){
31         int j = min( n/(n/i) , m/(m/i) );
32         ans += 1ll * (sum[j] - sum[i-1]) * (n/i) * (m / i);
33         i = j + 1;
34     }
35     return ans;
36 }
37 int main(){
38     init();
39     int T; scanf("%d",&T);
40     for(int cas = 1;cas<=T;++cas){
41         int a,b,c,d,k; scanf("%d %d %d %d %d",&a,&b,&c,&d,&k);
42         if(k==0){
43             printf("Case %d: 0\n",cas);
44             continue;
45         }
46         --a; --c;
47         b/=k; d/=k;
48         a/=k; c/=k;
49         printf("%lld\n",solve(b,d) - solve(a,d) - solve(c,b) + solve(a,c));
50     }
51     return 0;
52 }
View Code

 

hdu4746

求区间[1..n] 和 区间 [1..m]中gcd 的 质因子(带重复)的个数小于 P 的对数。

参考博客

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N = 5e5+9;
 5 bool vis[N];
 6 int mu[N],sum[27][N],cnt[N];
 7 vector<int> prim;
 8 void Mobi(){
 9     mu[1] = 1; cnt[1] = 0;
10     for(int i = 2;i<=N-9;++i){
11         if(!vis[i]){
12             prim.push_back(i);
13             mu[i] = -1;
14             cnt[i] = 1;
15         }
16         for(int j = 0;j<prim.size() && i * prim[j] <= N-9;++j){
17             vis[ i * prim[j] ] = 1;
18             cnt[ i * prim[j] ] = cnt[i] + 1;
19             if( i % prim[j] ) mu[ i * prim[j] ] = -mu[i];
20             else{
21                 mu[i * prim[j] ] = 0;
22                 break;
23             }
24         }
25     }
26 }
27 void init(){
28     for(int i = 1;i<=N-9;++i){
29         for(int j = i;j<= N - 9;j+=i){
30             sum[ cnt[i] ][ j ] += mu[j/i];
31         }
32     }
33     for(int i = 1;i<=N-9;++i){
34         for(int j = 1;j<=20;++j){
35             sum[j][i] += sum[j-1][i];
36         }
37     }
38     for(int i = 0;i<=20;++i){
39         for(int j = 1;j<=N-9;++j){
40             sum[i][j] += sum[i][j-1];
41         }
42     }
43 }
44 int main(){
45     Mobi();
46     init();
47     int T; scanf("%d",&T);
48     while(T--){
49         int n,m,p; scanf("%d %d %d",&n,&m,&p);
50         if(p>20){
51             printf("%lld\n",1ll * n * m);
52             continue;
53         }
54         if(n>m) swap(n,m);
55         ll ans = 0;
56         for(int i = 1;i<=n;){
57             int j = min( n/(n/i) , m/(m/i) );
58             ans += 1ll * (n/i) * (m/i) * (sum[p][j] - sum[p][i-1]);
59             i = j+1;
60             // cerr<<i<<" "<<j<<" "<<ans<<endl;
61         }
62         printf("%lld\n",ans);
63     }
64 }
View Code

 

hdu4675

给你一个长度n的数组a,a[i] <= m ,给你一个数k,对于 1 到 m 的 每个数 d ,询问有多少 序列 b 满足 b[i] != a[i] 的 i 仅有 k 个, 并且 gcd (b[1]...b[n]) == d

还是那个反演,只不过加了一个组合数。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N = 3e5+9;
 5 const ll modd = 1e9+7;
 6 vector<int> prim;
 7 bool vis[N];
 8 ll mu[N],a[N],F[N],X[N];
 9 ll jie[N];
10 int num[N];
11 int n,m,k;
12 ll ksm(ll a,ll b){
13     ll res = 1;
14     a %= modd;
15     while(b){
16         if(b&1) res = res * a % modd;
17         b>>=1;
18         a = a * a % modd;
19     }
20     return res;
21 }
22 ll C(ll n,ll m){
23     ll tem = jie[m] * jie[n-m] % modd;
24     return jie[n] * ksm(tem,modd-2) % modd;
25 }
26 void Mobi(){
27     mu[1] = 1;
28     for(int i = 2;i<=N-9;++i){
29         if(!vis[i]){
30             prim.push_back(i);
31             mu[i] = -1;
32         }
33         for(int j = 0;j<prim.size() && i * prim[j] <= N - 9;++j){
34             vis[ i * prim[j] ] = 1;
35             if( i% prim[j] ) mu[i * prim[j] ] = -mu[i];
36             else{
37                 mu[i*prim[j]] = 0;
38                 break;
39             }
40         }
41     }
42     jie[0] = 1;
43     for(int i = 1;i<=N-9;++i) jie[i] = jie[i-1] * i % modd;
44 }
45 void init(){
46     for(int i = 1;i<=m;++i) X[i] = n;
47     for(int i = 1;i<=m;++i){
48         for(int j = i;j<=m;j+=i){
49             X[i] -= num[j];
50         }
51     }
52     for(int i = 1;i<=m;++i){
53         if(X[i] > k) F[i] = 0;
54         else{
55             F[i] = ksm( m/i , X[i] ) * C(n-X[i],k-X[i]) % modd * ksm( m/i - 1 , k-X[i]) % modd; 
56         }
57     }
58 }
59 int main(){
60     Mobi();
61     while(~scanf("%d %d %d",&n,&m,&k)){
62         for(int i = 1;i<=m;++i) num[i] = 0;
63         for(int i = 1;i<=n;++i){
64             scanf("%lld",a+i);
65             num[ a[i] ]++;
66         }
67         init();
68         for(int i = 1;i<=m;++i){
69             ll ans = 0;
70             for(int j = i;j<=m;j+=i){
71                 ans = (ans + mu[j/i] * F[j] + modd)%modd;
72             }
73             printf("%lld%c",ans,i==m?'\n':' ');
74         }
75     }
76     return 0;
77 }
View Code

 

cf803F

给你一个长度n的数组a,问你有多少个 子序列满足 gcd == 1.同一个套路, 对于每个d,求出 有多少个 数 是 d 的倍数(num),直接 mu[d] * F(d) == mu[d] * ( ksm(2,num) -1 ) 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const ll modd = 1e9+7;
 5 const int N = 1e5+9;
 6 bool vis[N];
 7 vector<int> prim;
 8 ll mu[N],num[N];
 9 map<int,int> mp;
10 ll ksm(ll a,ll b){
11     ll res = 1;
12     while(b){
13         if(b&1) res = res * a % modd;
14         b>>=1;
15         a = a * a % modd;
16     }
17     return res;
18 }
19 void Mobi(){
20     mu[1] = 1;
21     for(int i = 2;i<=100000;++i){
22         if(!vis[i]){
23             prim.push_back(i);
24             mu[i] = -1;
25         }
26         for(int j = 0;j<prim.size() && i * prim[j] <= 100000;++j){
27             vis[ i * prim[j] ] = 1;
28             if(i%prim[j]) mu[ i * prim[j] ] = -mu[i];
29             else{
30                 mu[ i * prim[j] ] = 0;
31                 break;
32             }
33         }
34     }
35 }
36 int main(){
37     Mobi();
38     int n; scanf("%d",&n);
39     int m = 0;
40     for(int i = 1;i<=n;++i){
41         int a; scanf("%d",&a);
42         mp[a]++;
43         m = max(m,a);
44     }
45     for(int i = 1;i<=m;++i){
46         for(int j = i;j<=m;j+=i){
47             num[i] += mp[j];
48         }
49     }
50     ll ans = 0;
51     for(int i = 1;i<=m;++i){
52         ll tem = mu[i] * (ksm(2,num[i])-1);
53         ans = (ans + tem + modd) % modd;
54     }
55     printf("%lld",ans);
56     return 0;
57 }
View Code

 

hysbz2005

同样的套路

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N = 1e5+9;
 5 bool vis[N];
 6 vector<int> prim;
 7 ll f[N],mu[N];
 8 void init(){
 9     mu[1] = 1;
10     for(int i =2;i<=N-9;++i){
11         if(!vis[i]){
12             prim.push_back(i);
13             mu[i] = -1;
14         }
15         for(int j = 0;j<prim.size() && i * prim[j] <= N-9;++j){
16             vis[ i * prim[j] ] = 1;
17             if( i % prim[j] ) mu[i * prim[j] ] = -mu[i];
18             else{
19                 mu[ i * prim[j] ] = 0;
20                 break;
21             }
22         }
23     }
24 }
25 int main(){
26     init();
27     int n,m; scanf("%d %d",&n,&m);
28     for(int i = 1;i<=min(n,m);++i){
29         for(int j = i;j<=min(n,m);j+=i){
30             f[i] += mu[j/i] * (n/j) * (m/j);
31         }
32     }
33     ll ans = 0;
34     for(int i = 1;i<=min(n,m);++i){
35         ans += f[i] * (2*i-1);
36     }
37     printf("%lld",ans);
38     return 0;
39 }
View Code

 

嗯,刷了几道莫比乌斯的题目,算是可以自保了,毕竟不是数学选手,就学到这里算了。哈哈哈。

posted @ 2020-03-16 23:32  小布鞋  阅读(149)  评论(0编辑  收藏  举报