2020.8.17
今天第一次屁股坐在椅子上学莫比乌斯反演的一天。本来是想让队友学的,现在队友不知道换了多少人了,也没几个真的能靠得住的,还都得看自己。其实学到现在这个程度,除了一些极其吃天赋的问题,基本只有没认真学和学会两种内容,自己原先觉得不会,但是只要肯坐在椅子上学那是能学个十有八九的,所以还是得好好学的。
这个莫比乌斯函数就是用来加速求gcd,lcm,约数之类问题的问题。对于区间[1,i]和[1,j]的里面gcd为k的个数,我们有朴素n^2logn算法,对于超过1e5的数据这种算法显然过于疲软,那么我们就需要线性处理,首先莫比乌斯函数是什么怎么选写多了肯定也就会了,我数学不好(查三节课一个数学学位的假数学爱好者),推到过程略过,作为计算机选手只用掌握套路就行,那么根据gcd的性质,gcd(i,j)== k显然有 gcd(i / k , j / k) == 1,所以管用套路是使得n为m和n相对较小的那一个,枚举d从1到 n / k, 对于每个d对答案的贡献为
1
2miu[d] * F(d * k)//F(x) = (n / x) * (n / x)为莫比乌斯函数
然后就行了,这么一来是O(n)的,完美
洛谷P1390 公约数的和
这个题第二个求和表达式不是从1开始的,没用分块,直接重新推的,减掉就行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73#include <bits/stdc++.h> using namespace std; #define limit (2000000 + 5)//防止溢出 #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f #define lowbit(i) i&(-i)//一步两步 #define EPS 1e-6 #define FASTIO ios::sync_with_stdio(false);cin.tie(0); #define ff(a) printf("%dn",a ); #define pi(a,b) pair<a,b> #define rep(i, a, b) for(ll i = a; i <= b ; ++i) #define per(i, a, b) for(ll i = b ; i >= a ; --i) #define MOD 998244353 #define traverse(u) for(int i = head[u]; ~i ; i = edge[i].next) #define FOPEN freopen("C:\Users\tiany\CLionProjects\acm_01\data.txt", "rt", stdin) #define FOUT freopen("C:\Users\tiany\CLionProjects\acm_01\dabiao.txt", "wt", stdout) #define debug(x) cout<<x<<endl typedef long long int ll; #define int ll typedef unsigned long long ull; inline ll read(){ ll sign = 1, x = 0;char s = getchar(); while(s > '9' || s < '0' ){if(s == '-')sign = -1;s = getchar();} while(s >= '0' && s <= '9'){x = (x << 3) + (x << 1) + s - '0';s = getchar();} return x * sign; }//快读 void write(ll x){ if(x < 0) putchar('-'),x = -x; if(x / 10) write(x / 10); putchar(x % 10 + '0'); } int prime[limit],tot,num[limit],phi[limit],miu[limit]; void get_prime(const int &n = 1e6){ memset(num,1,sizeof(num)); num[1] = num[0] = 0; miu[1] = 1; rep(i,2,n){ if(num[i])prime[++tot] = i,miu[i] = -1,phi[i] = i - 1; for(int j = 1; j <= tot && prime[j] * i <= n ; ++j){ num[prime[j] * i] = 0; if(i % prime[j] == 0){ miu[i * prime[j]] = 0; break; }else{ miu[i * prime[j]] = -miu[i];//莫比乌斯函数 } } } }//素数筛 int n; ll calc(int x){ ll ans = 0; rep(d,1,n / x){ ans += miu[d] * (n / d / x) * (n / d / x); } return ans; } signed main() { #ifdef LOCAL FOPEN; #endif n = read(); get_prime(n); ll ans = 0; rep(i,1,n){ ans += i * calc(i); } ans -= ((n + 1) * n) / 2; ans /= 2; write(ans); return 0; }
遇到gcd == k的普通套路就是先降等号右边,然后莫比乌斯反演
洛谷P3455 ZAP-Queries
这个题问的是裸的莫反,所以莫比乌斯函数F(d) = (n / d) * (m / d)
然后就套上跑出来即可,但是注意朴素解容易超时,需要用到除法值域分块,也不难的,求出来miu的前缀和就行
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78#include <bits/stdc++.h> using namespace std; #define limit (50000 + 5)//防止溢出 #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f #define lowbit(i) i&(-i)//一步两步 #define EPS 1e-6 #define FASTIO ios::sync_with_stdio(false);cin.tie(0); #define ff(a) printf("%dn",a ); #define pi(a,b) pair<a,b> #define rep(i, a, b) for(ll i = a; i <= b ; ++i) #define per(i, a, b) for(ll i = b ; i >= a ; --i) #define MOD 998244353 #define traverse(u) for(int i = head[u]; ~i ; i = edge[i].next) #define FOPEN freopen("C:\Users\tiany\CLionProjects\acm_01\data.txt", "rt", stdin) #define FOUT freopen("C:\Users\tiany\CLionProjects\acm_01\dabiao.txt", "wt", stdout) #define debug(x) cout<<x<<endl typedef long long int ll; typedef unsigned long long ull; inline ll read(){ ll sign = 1, x = 0;char s = getchar(); while(s > '9' || s < '0' ){if(s == '-')sign = -1;s = getchar();} while(s >= '0' && s <= '9'){x = (x << 3) + (x << 1) + s - '0';s = getchar();} return x * sign; }//快读 void write(ll x){ if(x < 0) putchar('-'),x = -x; if(x / 10) write(x / 10); putchar(x % 10 + '0'); } int prime[limit],tot,num[limit],phi[limit],miu[limit]; void get_prime(const int &n = 1e6){ memset(num,1,sizeof(num)); num[1] = num[0] = 0; miu[1] = 1; rep(i,2,n){ if(num[i])prime[++tot] = i,miu[i] = -1,phi[i] = i - 1; for(int j = 1; j <= tot && prime[j] * i <= n ; ++j){ num[prime[j] * i] = 0; if(i % prime[j] == 0){ miu[i * prime[j]] = 0; break; }else{ miu[i * prime[j]] = -miu[i];//莫比乌斯函数 } } miu[i] += miu[i - 1]; } }//素数筛 ll n,m,d; ll F(ll x){ return (n / x) * (m / x);//莫比乌斯函数 } ll calc(){ ll ans = 0; for(int l = 1,r ; l <= min(n / d , m / d); l = r + 1){ //值域分块 ll t = n / d , s = m / d; r = min(t / (t / l), s / (s / l)); ans += (miu[r] - miu[l - 1]) * (t / l) * (s / l); } return ans; } int main() { #ifdef LOCAL FOPEN; #endif int t = read(); get_prime(5e4 + 1); while (t--){ n = read(),m = read(),d = read(); ll minn = min(n / d , m / d); ll ans = calc(); printf("%lldn",ans); } return 0; }
然后如果下界不为1的,有上下界差分的,比如我区去年区域赛的一道题,问两个区间有多少互质的数,可以转化为a-b和u-v的gcd==1的问题,来一遍莫反
莫比乌斯函数选取F(a,b,u,v) = ((b / k) - ((a - 1) / k)) * ((v / k) - ((u - 1)/ k))
然后理论上用值域分块即可,但不用好像也没啥关系
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71#include <bits/stdc++.h> using namespace std; #define limit (10000000 + 5)//防止溢出 #define INF 0x3f3f3f3f #define inf 0x3f3f3f3f3f #define lowbit(i) i&(-i)//一步两步 #define EPS 1e-6 #define FASTIO ios::sync_with_stdio(false);cin.tie(0); #define ff(a) printf("%dn",a ); #define pi(a,b) pair<a,b> #define rep(i, a, b) for(ll i = a; i <= b ; ++i) #define per(i, a, b) for(ll i = b ; i >= a ; --i) #define MOD 998244353 #define traverse(u) for(int i = head[u]; ~i ; i = edge[i].next) #define FOPEN freopen("C:\Users\tiany\CLionProjects\acm_01\data.txt", "rt", stdin) #define FOUT freopen("C:\Users\tiany\CLionProjects\acm_01\dabiao.txt", "wt", stdout) #define debug(x) cout<<x<<endl typedef long long int ll; typedef unsigned long long ull; inline ll read(){ ll sign = 1, x = 0;char s = getchar(); while(s > '9' || s < '0' ){if(s == '-')sign = -1;s = getchar();} while(s >= '0' && s <= '9'){x = (x << 3) + (x << 1) + s - '0';s = getchar();} return x * sign; }//快读 void write(ll x){ if(x < 0) putchar('-'),x = -x; if(x / 10) write(x / 10); putchar(x % 10 + '0'); } int prime[limit],tot,num[limit],phi[limit],miu[limit]; void get_prime(const int &n = 1e7){ memset(num,1,sizeof(num)); num[1] = num[0] = 0; miu[1] = 1; rep(i,2,n){ if(num[i])prime[++tot] = i,miu[i] = -1,phi[i] = i - 1; for(int j = 1; j <= tot && prime[j] * i <= n ; ++j){ num[prime[j] * i] = 0; if(i % prime[j] == 0){ miu[i * prime[j]] = 0; break; }else{ miu[i * prime[j]] = -miu[i];//莫比乌斯函数 } } } }//素数筛 ll d; ll n,m,a,b; ll F(ll k){ return ((n / k) - ((a - 1) / k)) * ((m / k) - ((b - 1)/ k));//莫比乌斯函数 } ll calc(){ ll ans = 0; for(ll k = 1; k <= min(n,m); ++k){ ans += miu[k] * F(k);//莫比乌斯反演 } return ans; } int main() { #ifdef LOCAL FOPEN; #endif get_prime(1e7 + 1); a = read(),n = read(),b = read(),m = read(),d =1; ll ans = calc(); write(ans); return 0; }
好啦,完结撒花,今天又是元气满满的一天
最后
以上就是美满吐司最近收集整理的关于数论变换 - 莫比乌斯反演篇的全部内容,更多相关数论变换内容请搜索靠谱客的其他文章。
发表评论 取消回复