题解:P11770 檐牙覆雪
思路参考:@chenwenmo 的题解。
单次询问 O(n \ln n) \to \Theta(n)
有个朴素的
设
取了多次
且
这样就有了单次询问
单次询问 \Theta(n) \to O(1)
要加速单次询问,要么是
观察转移方程
只有红色的部分与
再令
就是蓝色部分的贡献。
接下来考虑计算红色部分的贡献,记为
如果连边
那么红色部分的贡献就是
因为一个
交换求和顺序
每次新加入一个
我们递推维护
- 由于
siz 更新了,对于每个更新了的siz_k ,每个的增量是1 ,增加贡献\left\lfloor\dfrac{\color{red}n-1}{k}\right\rfloor ,因为每个k 会在k 的倍数处都算一遍,红色部分是为了不重复计算; - 另一部分是
\sum\limits_{j \ | \ n} siz_j - 1 ,这样的j 在2\cdot 10^6 的值域内是很少的,所以线性筛的时候记录每个数的最小质因数,\log 时间分解质因数,dfs 就可以\Theta(d(n)) 时间内找出所有n 的因数,d(n) 表示n 的因数个数。
那么
预处理
:::success[实现]
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
#ifdef ONLINE_JUDGE
#define debug(...) 0
#else
#define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)
#endif
const int N = 2e6 + 5;
int a[N];
int siz[N];
bool notprime[N];
vector<int> primes;
int d[N], mp[N];
ll pre[N];
ll ans[N];
vector<pii> divisors[N];
ll dfs(int u, vector<pii> &vec, int x) {
if (u == vec.size()) return siz[x] - 1;
ll res = 0;
for (int i = 0; i <= vec[u].second; i++)
res += dfs(u + 1, vec, x * pow(vec[u].first, i));
return res;
}
void sieve(int n) {
for (int i = 2; i <= n; i++) {
if (!notprime[i]) primes.emplace_back(i), d[i] = i, mp[i] = i;
for (int j = 0; primes[j] * i <= n; j++) {
notprime[primes[j] * i] = 1;
d[primes[j] * i] = d[i];
mp[primes[j] * i] = primes[j];
if (i % primes[j] == 0) break;
}
}
for (int i = 2; i <= n; i++) {
int t = i;
while (t > 1) {
int lst = mp[t], cnt = 0;
while (mp[t] == lst) cnt++, t /= mp[t];
divisors[i].emplace_back(lst, cnt);
}
}
d[1] = 1e9;
a[1] = pre[1] = ans[1] = siz[1] = 1;
ll sum = 0;
for (int i = 2; i <= n; i++) {
int j = i / d[i];
a[i] = a[j] - i / j + 1;
pre[i] = pre[i - 1] + a[i];
for (int k = i; k; k = k / d[k])
siz[k]++, sum += (i - 1) / k;
sum += dfs(0, divisors[i], 1);
ans[i] = pre[i] + sum;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
sieve(N - 5);
int tt;
cin >> tt;
while (tt--) {
int n; cin >> n;
cout << ans[n] << "\n";
}
return 0;
}
:::