[NOI2010] 能量采集 (莫比乌斯反演 欧拉反演)

题意:

2 ∗ ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) − n ∗ m 2*\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j)-n*m 2i=1nj=1mgcd(i,j)nm

分析:

莫比乌斯反演:

那么只需要求

∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) \sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j) i=1nj=1mgcd(i,j)

枚举每个 gcd ⁡ ( i , j ) \gcd(i,j) gcd(i,j) 的值

∑ d = 1 n d ∑ i = 1 n ∑ j = 1 m [ gcd ⁡ ( i , j ) = d ] \sum_{d=1}^{n}d\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)=d] d=1ndi=1nj=1m[gcd(i,j)=d]

再用莫比乌斯反演,和 problem b 这题是一样的

∑ d = 1 n d ∑ i = 1 ⌊ min ⁡ ( n , m ) d ⌋ μ ( i ) ⌊ n d ∗ i ⌋ ⌊ m d ∗ i ⌋ \sum_{d=1}^{n}d\sum_{i=1}^{\left \lfloor \frac{\min(n,m)}{d} \right \rfloor }\mu(i)\left \lfloor \frac{n}{d*i} \right \rfloor \left \lfloor \frac{m}{d*i} \right \rfloor d=1ndi=1dmin(n,m)μ(i)dindim

后面可用整除分块。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, m, mobius[N], primes[N], cnt, res, sum[N];
bool st[N];
void get_mobius(int n) {
    mobius[1] = 1;
    for (int i = 2; i <= n; i ++) {
        if (!st[i]) {
            primes[cnt ++] = i;
            mobius[i] = -1;
        }
        for (int j = 0; primes[j] * i <= n; j ++) {
            int t = primes[j] * i;
            st[t] = 1;
            if (i % primes[j] == 0) {
                mobius[t] = 0;
                break;
            }
            mobius[t] = -mobius[i];
        }
    }
    for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + mobius[i];
}
signed main() {
    get_mobius(N - 1);
    cin >> n >> m;
    for (int d = 1; d <= n; d ++) {
        int k = min(n, m) / d, ans = 0;
        for (int l = 1, r; l <= k; l = r + 1) {
            r = min(n / (n / l), m / (m / l));
            ans += (sum[r] - sum[l - 1]) * (n / (d * l)) * (m / (d * l));
        }
        res += d * ans;
    }
    cout << 2 * res - n * m << endl;
}

欧拉反演

定理:

n = ∑ d ∣ n φ ( d ) n=\sum_{d \mid n} \varphi(d) n=dnφ(d)

原式为

∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) \sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j) i=1nj=1mgcd(i,j)

gcd ⁡ ( i , j ) \gcd(i,j) gcd(i,j) 套用欧拉反演定理

∑ i = 1 n ∑ j = 1 m ∑ d ∣ gcd ⁡ ( i , j ) φ ( d ) \sum_{i=1}^{n}\sum_{j=1}^{m} \sum_{d \mid \gcd(i,j)}\varphi(d) i=1nj=1mdgcd(i,j)φ(d)

φ ( d ) \varphi(d) φ(d) 提到前面,枚举 d d d

∑ d = 1 n φ ( d ) ∑ i = 1 n ∑ j = 1 m [ d ∣ gcd ⁡ ( i , j ) ] \sum_{d=1}^{n}\varphi(d)\sum_{i=1}^{n}\sum_{j=1}^{m}[d \mid \gcd(i,j)] d=1nφ(d)i=1nj=1m[dgcd(i,j)]

化简后面

∑ d = 1 n φ ( d ) ⌊ n d ⌋ ⌊ m d ⌋ \sum_{d=1}^{n}\varphi(d)\left \lfloor \frac{n}{d} \right \rfloor \left \lfloor \frac{m}{d} \right \rfloor d=1nφ(d)dndm

预处理欧拉函数前缀和,用整除分块即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, m, primes[N], euler[N], cnt, res, sum[N];
bool st[N];
void get_eulers(int n) {
    euler[1] = 1;
    for (int i = 2; i <= n; i ++) {
        if (!st[i]) {
            primes[cnt ++] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++) {
            int t = primes[j] * i;
            st[t] = 1;
            if (i % primes[j] == 0) {
                euler[t] = primes[j] * euler[i];
                break;
            }
            euler[t] = (primes[j] - 1) * euler[i];
        }
    }
    for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + euler[i];
}
signed main() {
    get_eulers(N - 1);
    cin >> n >> m;
    for (int l = 1, r; l <= min(n, m); l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        res += (sum[r] - sum[l - 1]) * (n / l) * (m / l);
    }
    cout << 2 * res - n * m << endl;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值