LOJ6053 简单的函数
某一天,你发现了一个神奇的函数 \(f(x)\),它满足很多神奇的性质:
- \(f(1)=1\)。
- \(f(p^c)=p \oplus c\)(\(p\) 为质数,\(\oplus\) 表示异或)。
- \(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\) 使得
同时满足 \(h(p) = 0\) 且 \(h\) 函数是积性函数,即 \(h(1) = 1\)。
由于函数 \(f\) 在质数处满足
为了使 \(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)\) 的前缀和如何求得,不妨设 \(G(n) = \sum\limits_{i = 1}^{n}g(i)\),则
记 \(S_1(n) = \sum\limits_{i = 1}^{n}\varphi(i)\),\(S_2(n) = \sum\limits_{i = 1}^{n}\varphi(2i)\),则
而
由于 \(\varphi * {\mathbf{1}} = \rm{Id}\),我们可以利用杜教筛快速求出 \(S_1(n)\) 的值,而 \(S_2(n)\) 的值也可以通过递归计算。
回顾一下杜教筛如何求 \(\varphi(n)\) 的前缀和,因为 \(n = \sum\limits_{d \mid n}\varphi(d)\),因此
而右边那一坨可以使用数论分块求,因此我们就解决了这个问题。
时间复杂度 \(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 数和其两倍的并,不会显然改变复杂度,所以也是可以的。

浙公网安备 33010602011771号