LOJ6053 简单的函数

#6053. 简单的函数

某一天,你发现了一个神奇的函数 \(f(x)\),它满足很多神奇的性质:

  1. \(f(1)=1\)
  2. \(f(p^c)=p \oplus c\)\(p\) 为质数,\(\oplus\) 表示异或)。
  3. \(f(ab)=f(a)f(b)\)\(a\)\(b\) 互质)。

你看到这个函数之后十分高兴,于是就想要求出 \(\sum\limits_{i=1}^n f(i)\)
由于这个数比较大,你只需要输出 \(\sum\limits_{i=1}^n f(i) \bmod (10^9+7)\)


函数在质数幂处不满足积性,但在其他处均满足积性,考虑 PN 筛。

考虑构造易求的积性函数 \(g\) 使得

\[f(n) = \sum_{d \mid n}g(d)h\left(\frac{n}{d}\right) \]

同时满足 \(h(p) = 0\)\(h\) 函数是积性函数,即 \(h(1) = 1\)

由于函数 \(f\) 在质数处满足

\[f(p^c) = \begin{cases}p + 1, \, p = 2 \\ p - 1, \, p \neq 2\end{cases} \]

为了使 \(h(p) = 0\),考虑 \(f(p) = g(1)h(p) + g(p)h(1) = g(p)\),需要构造一个易于求前缀和的积性函数使得 \(g(2) = 3\)\(g(p) = p - 1(p \neq 2)\),容易想到 \(\varphi(n)\) 函数,因此我们构造积性函数

\[g(n) = \begin{cases}3\varphi(n), \, 2 \mid n \\ \varphi(n), \, 2 \nmid n\end{cases} \]

考虑 \(g(n)\) 的前缀和如何求得,不妨设 \(G(n) = \sum\limits_{i = 1}^{n}g(i)\),则

\[\begin{aligned}G(n) &= \sum_{i = 1}^{n}g(i) \\ &= \sum_{i = 1}^{n}\varphi(i) + 2\sum_{i = 1}^{\left\lfloor\frac{n}{2}\right\rfloor}\varphi(2i) \\ \end{aligned} \]

\(S_1(n) = \sum\limits_{i = 1}^{n}\varphi(i)\)\(S_2(n) = \sum\limits_{i = 1}^{n}\varphi(2i)\),则

\[G(n) = S_1(n) + 2S_2\left(\left\lfloor\frac{n}{2}\right\rfloor\right) \]

\[\begin{aligned}S_2(n) &= \sum_{i = 1}^{n}\varphi(2i) \\ &= 2\sum_{i = 1}^{\left\lfloor\frac{n}{2}\right\rfloor}\varphi(2i) + \sum_{i = 1}^{\left\lfloor\frac{n}{2}\right\rfloor}\varphi(2i - 1) \\ &= \sum_{i = 1}^{n}\varphi(i) + \sum_{i = 1}^{\left\lfloor\frac{n}{2}\right\rfloor}\varphi(2i) \\ &= S_1(n) + S_2\left(\left\lfloor\frac{n}{2}\right\rfloor\right)\end{aligned} \]

由于 \(\varphi * {\mathbf{1}} = \rm{Id}\),我们可以利用杜教筛快速求出 \(S_1(n)\) 的值,而 \(S_2(n)\) 的值也可以通过递归计算。

回顾一下杜教筛如何求 \(\varphi(n)\) 的前缀和,因为 \(n = \sum\limits_{d \mid n}\varphi(d)\),因此

\[\begin{aligned}\sum_{i = 1}^{n}i &= \sum_{i = 1}^{n}\sum_{d \mid i}\varphi(d){\mathbf{1}}\left(\frac{i}{d}\right) \\ \sum_{i = 1}^{n}i &= \sum_{d = 1}^{n}{\mathbf{1}}(d)\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\varphi(i) \\ \frac{n(n + 1)}{2} &= \sum_{i = 1}^{n}\varphi(i) + \sum_{d = 2}^{n}\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\varphi(i) \\ \sum_{i = 1}^{n}\varphi(n) &= \frac{n(n + 1)}{2} - \sum_{d = 2}^{n}\sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor}\varphi(i)\end{aligned} \]

而右边那一坨可以使用数论分块求,因此我们就解决了这个问题。

时间复杂度 \(O(\sqrt{n}\log{n})\)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define ld long double
#define pb push_back
#define PII pair<ll, ll>
#define PPI pair<ll, PII>
#define vi vector<ll>
#define vvi vector<vector<ll>>
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define rev(f, n) reverse(f, f + (n))
const int N = 1e6 + 10, mod = 1e9 + 7;
int cnt, primes[N], st[N];
ll n, phi[N], s1[N], s2[N], ans;
unordered_map<ll, ll> _s1, _s2, h[N];

ll qpow(ll a, ll k = mod - 2) {
    ll res = 1;
    while (k) {
        if (k & 1) res = res * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return res;
}

void init() {
    phi[1] = 1;
    for (int i = 2; i < N; i ++ ) {
        if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1;
        for (int j = 0; j < cnt && i * primes[j] < N; j ++ ) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j] % mod;
                break;
            }
            phi[i * primes[j]] = phi[i] * phi[primes[j]] % mod;
        }
    }
    for (int i = 1; i < N; i ++ ) {
        s1[i] = (s1[i - 1] + phi[i]) % mod;
        if (i % 2 == 0) s2[i >> 1] = (s2[(i >> 1) - 1] + phi[i]) % mod;
    }
    for (int i = 0; i < cnt; i ++ ) h[i][0] = 1, h[i][1] = 0;
}

ll get_s1(ll n) {
    if (n < N) return s1[n];
    if (_s1.count(n)) return _s1[n];
    ll res = (n % mod) * ((n + 1) % mod) / 2;
    for (ll l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res = (res - (r - l + 1) * get_s1(n / l) % mod + mod) % mod;
    }
    return _s1[n] = res;
}

ll get_s2(ll n) {
    if (n < N >> 1) return s2[n];
    if (_s2.count(n)) return _s2[n];
    return _s2[n] = (get_s1(n) + get_s2(n >> 1)) % mod;
}

ll get_g(ll n) {
    return (get_s1(n) + 2 * get_s2(n >> 1)) % mod;
}

void dfs_PN(int idx, ll now, ll val) {
    (ans += val * get_g(n / now) % mod) %= mod;
    if (idx == cnt || now * primes[idx] > n) return;
    for (int i = idx; i < cnt; i ++ ) {
        if (now > n / primes[i] / primes[i]) break;
        ll x = now * primes[i];
        for (int j = 1; x <= n; j ++ , x *= primes[i]) {
            if (!h[i].count(j)) {
                ll res = primes[i] ^ j, g = primes[i] - 1;
                for (int k = 1; k <= j; k ++ ) {
                    if (!i) res = (res - h[i][j - k] * g * 3 % mod + mod) % mod;
                    else res = (res - h[i][j - k] * g % mod + mod) % mod;
                    g = g * primes[i] % mod;
                }
                h[i][j] = res;
            }
            if (h[i][j]) dfs_PN(i + 1, x, val * h[i][j] % mod);
        }
    }
}

void solve() {
    init();
    cin >> n;
    dfs_PN(0, 1, 1);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}

什么?你说你这样设 \(g(n)\) 求不出前缀和,\(S\) 函数的变化不好想?我们直接让 \(g(n) = \varphi(n)\) 也是可以的,不过在此条件下,合法的 \(h(n)\) 取值变多了,也就是说变成了 PN 数和其两倍的并,不会显然改变复杂度,所以也是可以的。

posted @ 2025-09-08 21:25  YipChip  阅读(17)  评论(0)    收藏  举报