莫比乌斯函数的应用

 [NOI2016]循环之美

将题目的本质提出来,就是让我们求:

$$\sum_{i=1}^{m} [gcd(i, k) = 1] \sum_{j=1}^{n} [gcd(i, j) = 1]$$

看到$gcd = 1$这种形式,我们马上就想到了莫比乌斯函数。

通常提到莫比乌斯,我们大多使用它的性质来进行反演,但是我们很容易发现这题无法进行反演(只有一个变量)。

在这里,我们不考虑使用反演的方式,而是从莫比乌斯函数的性质来解决问题。

我们知道莫比乌斯函数有性质:$\sum_{d|n} \mu(d) = [n = 1]$

把这个性质代入到上式中,有:

$$\sum_{i=1}^{m} \sum_{d|i, d|k} \mu(d) \sum_{j=1}^{n} \sum_{t|i, t|j} \mu(t)$$

考虑我们经常使用的更换枚举对象,把枚举约数转化为枚举倍数($j$的意义变为$t$的$j$倍),得到:

$$\sum_{i=1}^{m} \sum_{d|i, d|k} \mu(d) \sum_{t|i}^n \mu(t) \sum_{j=1}^{\frac{n}{t}}$$

 即:

$$\sum_{i=1}^{m} \sum_{d|i, d|k} \mu(d) \sum_{t|i}^{n} \mu(t) \frac{n}{t}$$

主要到这里还有$t|i$的限制,于是我们继续把$t$往前提(同样改变了$i$的意义):

$$\sum_{t=1}^{min(n,m)} \mu(t) \frac{n}{t} \sum_{i=1}^{\frac{m}{t}} \sum_{d|it, d|k} \mu(d)$$

现在解决了枚举$t$的问题,又出现了枚举$d$的问题,那就再把$d$往前提一下:

$$\sum_{d|k} \mu(d) \sum_{t = 1}^{min(n,m)} \mu(t) \frac{n}{t} \sum_{i|,d|it}^{\frac{m}{t}}$$

令$w = gcd(t, d)$,则:

$$\sum_{d|k} \mu(d) \sum_{t = 1}^{min(n,m)} \mu(t) \frac{n}{t} \frac{mw}{td}$$

 由于$k ≤ 2000$,所以$mu(d) ≠ 0$的$d$最多只有$16$个,线性筛出$\mu$,然后枚举$d$和$t$。

这样复杂度大概是$O(16min(n,m))$,能拿到$72pts$。

继续做下去需要用到杜教筛,就先鸽一下。下面是$72$分的代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 #define int long long
 5 const int MAXN = 10000010;
 6 
 7 int cnt, prim[MAXN], mu[MAXN], vis[MAXN];
 8 
 9 void get_mu(int n)
10 {
11     mu[1] = 1;
12     for(int i = 2; i <= n; ++i)
13     {
14         if(!vis[i]) {prim[++cnt] = i; mu[i] = -1;}
15         for(int j = 1; j <= cnt && i * prim[j] <= n; ++j)
16         {
17             vis[i * prim[j]] = 1;
18             if(i % prim[j] == 0) break;
19             mu[i * prim[j]] = -mu[i];
20         }
21     }
22 }
23 
24 int gcd(int a, int b)
25 {
26     if(!b) return a;
27     return gcd(b, a % b);
28 }
29 
30 int n, m, k, ans;
31 
32 int solve(int d)
33 {
34     int sum = 0;
35     for(int t = 1; t <= min(n, m); ++t)
36         sum += mu[t] * (n / t) * (m * gcd(t, d) / (t * d));
37     return sum;
38 }
39 
40 signed main()
41 {
42     scanf("%lld %lld %lld", &n, &m, &k);
43     get_mu(max(k, min(n, m)));
44     for(int i = 1; i * i <= k; ++i)
45         if(k % i == 0)
46         {
47             if(mu[i] != 0) ans += mu[i] * solve(i);
48             if(i != k / i && mu[k / i] != 0) ans += mu[k / i] * solve(k / i);
49         }
50     printf("%lld\n", ans);
51     return 0;
52 }

 


 

转载于:https://www.cnblogs.com/Aegir/p/11115594.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值